본문 바로가기
기술의기록

5분만에 완성하는 Vercel Workflow 완벽 가이드

by Jeremy Winchester 2025. 10. 27.
반응형

AI 에이전트를 만들고 싶은데 복잡한 메시지 큐나 재시도 로직 때문에 막막하셨나요? 혹시 서버리스 환경에서 장기 실행 프로세스를 안정적으로 관리하는 방법을 찾고 계신가요?

2025년 10월, Vercel이 공개한 **Workflow Development Kit(WDK)**가 이 모든 고민을 한 번에 해결해줍니다! 단 두 줄의 코드만으로 여러분의 비동기 함수를 프로덕션급 워크플로우로 변신시킬 수 있어요.

오늘은 복잡한 분산 시스템 지식 없이도 안정적인 워크플로우를 구축할 수 있는 Vercel Workflow의 모든 것을 알려드릴게요! 💪

🎯 Vercel Workflow가 뭔가요?

Vercel Workflow(정식 명칭: Workflow Development Kit)는 비동기 JavaScript 함수에 **내구성(durability)**과 **안정성(reliability)**을 부여하는 TypeScript 기반 오픈소스 프레임워크예요.

2025년 10월 23일 공개 베타로 출시되었으며, 함수가 몇 분에서 몇 달 동안 일시 중지했다가 정확히 멈춘 지점에서 재개될 수 있도록 해주는 혁신적인 기술입니다. VercelVercel

🌟 왜 Vercel Workflow가 필요할까요?

전통적으로 안정적인 비동기 작업을 구현하려면 이런 것들이 필요했어요:

기존 방식의 문제점

  • 메시지 큐 설정과 관리
  • 복잡한 재시도 로직 구현
  • 상태 지속성을 위한 데이터베이스 설계
  • 실패 시 복구 메커니즘 구축
  • 분산 시스템 전문 지식

Vercel Workflow의 해결책 메시지 큐, 재시도 로직, 지속성 레이어를 자동으로 처리하여 비즈니스 로직 작성 시간보다 인프라 구축 시간이 더 오래 걸리는 문제를 해결합니다. Vercel

💡 핵심 개념: "use workflow"와 "use step"

Vercel Workflow의 마법은 단 두 개의 지시어(directive)에서 시작돼요!

1️⃣ "use workflow" - 워크플로우 정의

 
 
typescript
export async function handleUserSignup(email: string) {
  "use workflow";  // 🎯 이 한 줄이 마법의 시작!
  
  const user = await createUser(email);
  await sendWelcomeEmail(user);
  
  await sleep("5s");  // 5초 동안 일시 중지 (리소스 소비 없음!)
  await sendOnboardingEmail(user);
  
  return { userId: user.id, status: "onboarded" };
}

"use workflow" 지시어는 일반 비동기 함수를 durable workflow로 변환하며, 여러 단계를 조율하는 오케스트레이션 역할을 합니다. Vercel

2️⃣ "use step" - 작업 단위 정의

 
 
typescript
async function createUser(email: string) {
  "use step";  // 💪 자동으로 재시도되는 작업 단위
  
  console.log(`Creating user with email: ${email}`);
  
  // 데이터베이스 호출, API 요청 등 모든 Node.js 기능 사용 가능
  return { id: crypto.randomUUID(), email };
}

async function sendWelcomeEmail(user: { id: string; email: string }) {
  "use step";
  
  console.log(`Sending welcome email to user: ${user.id}`);
  
  if (Math.random() < 0.3) {
    // 에러 발생 시 자동으로 재시도!
    throw new Error("Retryable!");
  }
}

각 스텝은 독립적으로 실행되며 진행 상황을 자동으로 저장하고, 실패 시 자동으로 재시도됩니다. 스텝이 실행되는 동안 워크플로우는 리소스를 소비하지 않고 일시 중지됩니다. Vercel

🚀 Vercel Workflow 시작하기

Step 1: 프로젝트 설정

Next.js 프로젝트에 Vercel Workflow를 추가하는 건 정말 간단해요!

 
 
bash
# 패키지 설치
npm install workflow

# 또는
pnpm add workflow

Step 2: 첫 번째 워크플로우 만들기

workflows/user-signup.ts 파일을 생성해주세요:

 
 
typescript
import { sleep, FatalError } from "workflow";

export async function handleUserSignup(email: string) {
  "use workflow";
  
  // 1단계: 사용자 생성
  const user = await createUser(email);
  
  // 2단계: 환영 이메일 발송
  await sendWelcomeEmail(user);
  
  // 3단계: 5초 대기 (리소스 소비 없음!)
  await sleep("5s");
  
  // 4단계: 온보딩 이메일 발송
  await sendOnboardingEmail(user);
  
  return { userId: user.id, status: "onboarded" };
}

async function createUser(email: string) {
  "use step";
  
  console.log(`Creating user: ${email}`);
  return { id: crypto.randomUUID(), email };
}

async function sendWelcomeEmail(user: { id: string; email: string }) {
  "use step";
  
  console.log(`Sending welcome email to: ${user.id}`);
  
  // 30% 확률로 에러 발생 (자동 재시도됨)
  if (Math.random() < 0.3) {
    throw new Error("Email service temporarily unavailable");
  }
}

async function sendOnboardingEmail(user: { id: string; email: string }) {
  "use step";
  
  // 이메일 형식 검증
  if (!user.email.includes("@")) {
    // FatalError는 재시도하지 않음
    throw new FatalError("Invalid email format");
  }
  
  console.log(`Sending onboarding email to: ${user.id}`);
}

Step 3: API 라우트에서 워크플로우 실행

app/api/signup/route.ts 파일을 생성하세요:

 
 
typescript
import { start } from 'workflow/api';
import { handleUserSignup } from "@/workflows/user-signup";
import { NextResponse } from "next/server";

export async function POST(request: Request) {
  const { email } = await request.json();
  
  // 워크플로우를 비동기로 실행 (앱을 블로킹하지 않음)
  await start(handleUserSignup, [email]);
  
  return NextResponse.json({
    message: "User signup workflow started",
  });
}

start 함수는 워크플로우를 큐에 추가하고 즉시 반환하며, 완료를 기다리지 않습니다. Vercel

🎨 강력한 기능들

1. 웹훅으로 외부 이벤트 대기

결제 확인이나 사용자 액션 같은 외부 이벤트를 기다려야 할 때가 있죠?

 
 
typescript
import { createWebhook } from "workflow";

export async function paymentWorkflow(orderId: string) {
  "use workflow";
  
  // 웹훅 생성
  const webhook = createWebhook();
  console.log('Send payment confirmation to:', webhook.url);
  
  // 결제 확인이 올 때까지 대기 (폴링이나 큐 없이!)
  const request = await webhook;
  
  const paymentData = await request.json();
  console.log('Payment confirmed:', paymentData);
  
  return { orderId, status: "paid" };
}

웹훅을 사용하면 외부 서비스의 데이터가 도착할 때까지 워크플로우를 일시 중지할 수 있으며, 폴링이나 메시지 큐, 상태 관리가 필요 없습니다. Vercel

2. 장기 실행 프로세스

며칠이나 몇 주 동안 실행되는 프로세스도 쉽게 만들 수 있어요!

 
 
typescript
import { sleep } from "workflow";

export async function loyaltyRewardWorkflow(userId: string) {
  "use workflow";
  
  // 사용자가 라이드를 완료했다고 가정
  console.log(`Ride completed for user: ${userId}`);
  
  // 3일 동안 대기 (리소스 소비 없음!)
  await sleep("3 days");
  
  // 충성도 리워드 발급
  await issueLoyaltyReward(userId);
  
  return { userId, reward: "issued" };
}

async function issueLoyaltyReward(userId: string) {
  "use step";
  
  console.log(`Issuing loyalty reward to user: ${userId}`);
  // 리워드 발급 로직...
}

워크플로우는 몇 분 또는 몇 달 동안 일시 중지되었다가 정확히 멈춘 지점에서 재개될 수 있으며, 이 동안 리소스를 소비하거나 상태를 잃지 않습니다. Vercel

3. 고급 에러 핸들링

 
 
typescript
import { FatalError, RetryableError, getStepMetadata } from "workflow";

async function callExternalAPI(endpoint: string) {
  "use step";
  
  const metadata = getStepMetadata();
  const response = await fetch(endpoint);
  
  // 서버 에러 (5xx) - 지수 백오프로 재시도
  if (response.status >= 500) {
    throw new RetryableError("Server error", {
      retryAfter: metadata.attempt ** 2,  // 1초, 4초, 9초...
    });
  }
  
  // 404 에러 - 재시도하지 않음
  if (response.status === 404) {
    throw new FatalError("Resource not found. Skipping retries.");
  }
  
  // Rate limit (429) - Retry-After 헤더 사용
  if (response.status === 429) {
    const retryAfter = response.headers.get("Retry-After");
    throw new RetryableError("Rate limited", {
      retryAfter: parseInt(retryAfter),
    });
  }
  
  return response.json();
}

// 최대 재시도 횟수 설정
callExternalAPI.maxRetries = 5;

4. 타입 안전한 Hook 시스템

 
 
typescript
import { defineHook } from "workflow";

// Hook 타입 정의
type ApprovalRequest = {
  requestId: string;
  approved: boolean;
  approvedBy: string;
  comment: string;
};

const approvalHook = defineHook<ApprovalRequest>();

// 워크플로우에서 사용
export async function documentApprovalWorkflow(documentId: string) {
  "use workflow";
  
  const hook = approvalHook.create({
    token: `approval:${documentId}`
  });
  
  // 승인을 기다림
  const approval = await hook;
  
  console.log(`Document ${approval.requestId} ${approval.approved ? "approved" : "rejected"}`);
  console.log(`By: ${approval.approvedBy}, Comment: ${approval.comment}`);
}

// API 라우트에서 Hook 재개
export async function POST(request: Request) {
  const { documentId, ...approvalData } = await request.json();
  
  // 타입 안전성 보장!
  await approvalHook.resume(`approval:${documentId}`, approvalData);
  
  return new Response("OK");
}

🔍 워크플로우 실행 모니터링

워크플로우의 상태를 확인하고 결과를 가져오는 것도 간단해요:

 
 
typescript
import { start, getRun } from 'workflow/api';

// 워크플로우 시작
const run = await start(handleUserSignup, ['user@example.com']);
console.log('Run ID:', run.runId);

// 나중에 상태 확인
const existingRun = getRun(run.runId);
const status = await existingRun.status;

console.log('Status:', status); // 'pending', 'running', 'completed', 'failed'

// 결과 가져오기
try {
  const result = await existingRun.returnValue;
  console.log('Result:', result);
} catch (error) {
  if (error instanceof WorkflowRunFailedError) {
    console.error('Workflow failed:', error.message);
  }
}

첫 번째 트리거부터 최종 결과까지 워크플로우 내부에서 발생하는 모든 것이 이벤트 로그에 저장되며, 포함된 CLI와 Web UI를 통해 시각적으로 확인할 수 있습니다. Vercel

🎯 실전 활용 사례

1. AI 에이전트 구축

 
 
typescript
import { sleep } from "workflow";

export async function aiAgentWorkflow(taskId: string) {
  "use workflow";
  
  // 1단계: 작업 분석
  const analysis = await analyzeTask(taskId);
  
  // 2단계: AI 처리
  const aiResult = await processWithAI(analysis);
  
  // 3단계: 결과 검증 대기 (사람의 검토)
  await sleep("2 hours");
  
  // 4단계: 최종 처리
  return await finalizeResult(aiResult);
}

2. 데이터 파이프라인

 
 
typescript
export async function dataPipelineWorkflow(datasetId: string) {
  "use workflow";
  
  // 데이터 추출
  const rawData = await extractData(datasetId);
  
  // 데이터 변환
  const transformedData = await transformData(rawData);
  
  // 데이터 적재
  await loadData(transformedData);
  
  // 검증
  return await validatePipeline(datasetId);
}

3. 사용자 온보딩 플로우

 
 
typescript
import { sleep } from "workflow";

export async function onboardingWorkflow(userId: string) {
  "use workflow";
  
  await sendWelcomeEmail(userId);
  await sleep("1 day");
  
  await sendTipEmail(userId);
  await sleep("3 days");
  
  await sendFeedbackRequest(userId);
  await sleep("7 days");
  
  await sendPremiumOffer(userId);
}

💻 로컬 개발 환경 설정

Vercel Workflow는 로컬에서도 완벽하게 동작해요!

 
 
bash
# 로컬 개발 서버 실행
npm run dev

# 워크플로우 상태 확인 (CLI)
npx workflow list

# 특정 워크플로우 상세 정보
npx workflow get <run-id>

🚀 프로덕션 배포

Vercel에 배포하면 모든 것이 자동으로 설정돼요:

 
 
bash
# Vercel에 배포
vercel deploy

# 프로덕션 배포
vercel --prod

Vercel Workflow는 어떤 프레임워크, 플랫폼, 런타임에서도 실행되며, 배포나 크래시 상황에서도 살아남아 정확히 멈춘 지점에서 재개됩니다. Vercel

🔥 Vercel Workflow vs 기존 솔루션

기능Vercel Workflow기존 솔루션 (Bull, Temporal 등)

학습 곡선 매우 낮음 (단 2개 지시어) 높음 (복잡한 설정)
인프라 완전 관리형 직접 구축 필요
코드 스타일 일반 TypeScript YAML, 상태 머신
장기 실행 ✅ 내장 지원 추가 설정 필요
관찰성 ✅ 내장 UI/CLI 별도 도구 필요
가격 사용량 기반 인프라 비용

📚 더 알아보기

공식 문서

추천 학습 경로

  1. ✅ 이 가이드로 기본 개념 이해
  2. 📖 공식 문서의 Getting Started 따라하기
  3. 💻 간단한 프로젝트로 실습
  4. 🚀 프로덕션에 점진적으로 적용

🎁 마무리하며

Vercel Workflow는 복잡한 분산 시스템 지식 없이도 안정적이고 확장 가능한 비동기 워크플로우를 구축할 수 있게 해주는 게임 체인저예요. **"use workflow"**와 "use step" 단 두 개의 지시어만으로 프로덕션급 시스템을 만들 수 있다니, 정말 놀랍지 않나요? 😊

특히 AI 에이전트나 장기 실행 프로세스를 구축하는 분들에게는 필수 도구가 될 거라고 확신해요. 메시지 큐 설정하고, 재시도 로직 짜고, 상태 관리하느라 고생하던 시간을 이제 비즈니스 로직 개발에 투자할 수 있어요!

 

반응형