๋ฉ”์ธ ์ฝ˜ํ…์ธ ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
Step์€ Workflow๋ฅผ ์ž‘์€ ์‹คํ–‰ ๋‹จ์œ„๋กœ ๋‚˜๋ˆ„๋Š” ํ•ต์‹ฌ ๊ฐœ๋…์ž…๋‹ˆ๋‹ค. ๊ฐ Step์€ ๋…๋ฆฝ์ ์œผ๋กœ ์‹คํ–‰๋˜๊ณ  ์‹คํŒจ ์‹œ ํ•ด๋‹น Step๋ถ€ํ„ฐ ์žฌ์‹œ๋„ํ•  ์ˆ˜ ์žˆ์–ด, ๊ธด ์ž‘์—…์„ ์•ˆ์ „ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Step์ด ํ•„์š”ํ•œ ์ด์œ 

Workflow์—์„œ Step์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ฉด, ์ž‘์—… ์ค‘๊ฐ„์— ์‹คํŒจํ–ˆ์„ ๋•Œ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋‹ค์‹œ ์‹คํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Step์„ ์‚ฌ์šฉํ•˜๋ฉด ์‹คํŒจํ•œ ์ง€์ ๋ถ€ํ„ฐ ์žฌ๊ฐœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
// โŒ Step ์—†์ด: ์ค‘๊ฐ„์— ์‹คํŒจํ•˜๋ฉด ์ฒ˜์Œ๋ถ€ํ„ฐ
export const processOrder = workflow(
  { name: "process_order" },
  async ({ input }) => {
    await validatePayment(input.orderId);      // 1. ๊ฒฐ์ œ ๊ฒ€์ฆ
    await updateInventory(input.items);        // 2. ์žฌ๊ณ  ์—…๋ฐ์ดํŠธ
    await sendConfirmationEmail(input.email);  // 3. ์ด๋ฉ”์ผ ๋ฐœ์†ก
    // 3๋ฒˆ์—์„œ ์‹คํŒจํ•˜๋ฉด? โ†’ 1๋ฒˆ๋ถ€ํ„ฐ ๋‹ค์‹œ ์‹คํ–‰
  }
);

// โœ… Step์œผ๋กœ ๋‚˜๋ˆ„๊ธฐ: ์‹คํŒจํ•œ step๋ถ€ํ„ฐ ์žฌ์‹œ๋„
export const processOrder = workflow(
  { name: "process_order" },
  async ({ input, step }) => {
    await step.define({ name: "validate_payment" }, async () => {
      await validatePayment(input.orderId);
    }).run();
    
    await step.define({ name: "update_inventory" }, async () => {
      await updateInventory(input.items);
    }).run();
    
    await step.define({ name: "send_email" }, async () => {
      await sendConfirmationEmail(input.email);
    }).run();
    // 3๋ฒˆ์—์„œ ์‹คํŒจํ•˜๋ฉด? โ†’ 3๋ฒˆ๋ถ€ํ„ฐ ์žฌ์‹œ๋„
  }
);
Step ์‚ฌ์šฉ์˜ ์žฅ์ :
  • ๋ถ€๋ถ„ ์žฌ์‹œ๋„: ์‹คํŒจํ•œ step๋ถ€ํ„ฐ ๋‹ค์‹œ ์‹คํ–‰
  • ์ง„ํ–‰ ์ƒํ™ฉ ์ถ”์ : ๊ฐ step์˜ ์‹คํ–‰ ์‹œ๊ฐ„๊ณผ ์ƒํƒœ ํ™•์ธ
  • ๋””๋ฒ„๊น… ์šฉ์ด: ์–ด๋А step์—์„œ ์‹คํŒจํ–ˆ๋Š”์ง€ ๋ช…ํ™•ํžˆ ํŒŒ์•…

step.define()

step.define()์€ ํ•จ์ˆ˜๋ฅผ step์œผ๋กœ ๊ฐ์‹ธ๋Š” ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ๋น„๋™๊ธฐ ํ•จ์ˆ˜๋ฅผ step์œผ๋กœ ๋งŒ๋“ค๋ฉด ์‹คํ–‰ ์ด๋ ฅ์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.
export const sendEmail = workflow(
  { name: "send_email" },
  async ({ input, step }) => {
    // Step ์ •์˜ ๋ฐ ์‹คํ–‰
    await step.define(
      { name: "send" },
      async () => {
        await emailService.send({
          to: input.email,
          subject: "Hello",
          body: "...",
        });
      }
    ).run();
  }
);
ํ•ต์‹ฌ ๊ฐœ๋…:
  • name: Step ์‹๋ณ„์ž (workflow ๋‚ด์—์„œ ๊ณ ์œ ํ•ด์•ผ ํ•จ)
  • ๋น„๋™๊ธฐ ํ•จ์ˆ˜: ์‹ค์ œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์ฝ”๋“œ
  • .run(): Step์„ ์‹คํ–‰ํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜

๋ฐ˜ํ™˜๊ฐ’ ์‚ฌ์šฉ

Step์˜ ๋ฐ˜ํ™˜๊ฐ’์„ ๋‹ค์Œ step์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ step์€ ๋…๋ฆฝ์ ์œผ๋กœ ์ €์žฅ๋˜์ง€๋งŒ, ๋ฐ์ดํ„ฐ๋Š” ๋ฉ”๋ชจ๋ฆฌ์—์„œ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค.
export const processData = workflow(
  { name: "process_data" },
  async ({ input, step }) => {
    // Step 1: ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
    const data = await step.define(
      { name: "fetch_data" },
      async () => {
        return await fetchDataFromAPI(input.userId);
      }
    ).run();
    
    // Step 2: ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ (์ด์ „ step ๊ฒฐ๊ณผ ์‚ฌ์šฉ)
    const transformed = await step.define(
      { name: "transform" },
      async () => {
        return transformData(data);
      }
    ).run();
    
    // Step 3: ์ €์žฅ
    await step.define(
      { name: "save" },
      async () => {
        await saveToDatabase(transformed);
      }
    ).run();
    
    return { count: transformed.length };
  }
);
๋ฐ์ดํ„ฐ ํ๋ฆ„:
  1. fetch_data step์ด data ๋ฐ˜ํ™˜
  2. data๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ์—์„œ transform step์œผ๋กœ ์ „๋‹ฌ
  3. transformed๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ์—์„œ save step์œผ๋กœ ์ „๋‹ฌ
Step ์žฌ์‹œ๋„ ์‹œ์—๋Š” ์ด์ „ step์˜ ๊ฒฐ๊ณผ๊ฐ€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๋ณต์›๋˜์–ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

step.get()

step.get()์€ Model ๋ฉ”์„œ๋“œ๋ฅผ step์œผ๋กœ ์‹คํ–‰ํ•˜๋Š” ํŽธ๋ฆฌํ•œ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ๋ณ„๋„๋กœ ํ•จ์ˆ˜๋ฅผ ๊ฐ์‹ธ์ง€ ์•Š์•„๋„ ๋˜์–ด ์ฝ”๋“œ๊ฐ€ ๊ฐ„๊ฒฐํ•ด์ง‘๋‹ˆ๋‹ค.
import { BaseModel, workflow } from "sonamu";

class UserModelClass extends BaseModel {
  async sendWelcomeEmail(userId: number) {
    const user = await this.findById(userId);
    await emailService.send({
      to: user.email,
      subject: "Welcome!",
      body: "...",
    });
  }
}

export const onboardUser = workflow(
  { name: "onboard_user" },
  async ({ input, step }) => {
    // Model ๋ฉ”์„œ๋“œ๋ฅผ step์œผ๋กœ ์‹คํ–‰
    await step.get(
      UserModel,
      "sendWelcomeEmail"
    ).run(input.userId);
  }
);
์‚ฌ์šฉ ์‹œ๋‚˜๋ฆฌ์˜ค:
  • Model์˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์žฌ์‚ฌ์šฉ
  • ์ฝ”๋“œ ์ค‘๋ณต ์ œ๊ฑฐ
  • ํƒ€์ž… ์•ˆ์ „์„ฑ ์œ ์ง€

์ปค์Šคํ…€ ์ด๋ฆ„ ์ง€์ •

Step ์ด๋ฆ„์„ ๋ช…์‹œ์ ์œผ๋กœ ์ง€์ •ํ•˜์—ฌ ๋กœ๊ทธ๋ฅผ ์ฝ๊ธฐ ์‰ฝ๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
await step.get(
  { name: "send_email" },  // ์ปค์Šคํ…€ ์ด๋ฆ„
  UserModel,
  "sendWelcomeEmail"
).run(userId);

๋ฐ˜ํ™˜๊ฐ’ ํ™œ์šฉ

Model ๋ฉ”์„œ๋“œ์˜ ๋ฐ˜ํ™˜๊ฐ’๋„ ๋™์ผํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
const user = await step.get(
  UserModel,
  "findById"
).run(userId);

console.log(user.email);

step.sleep()

step.sleep()์€ ์ผ์ • ์‹œ๊ฐ„ ๋Œ€๊ธฐํ•˜๋Š” step์ž…๋‹ˆ๋‹ค. ์žฌ์‹œ๋„ ๊ฐ„ ์ง€์—ฐ, API rate limit ํšŒํ”ผ, ์ •๊ธฐ ํด๋ง ๋“ฑ์— ํ™œ์šฉํ•ฉ๋‹ˆ๋‹ค.
export const retryWithDelay = workflow(
  { name: "retry_with_delay" },
  async ({ input, step, logger }) => {
    for (let i = 0; i < 3; i++) {
      try {
        await step.define(
          { name: `attempt_${i + 1}` },
          async () => {
            await unreliableAPICall(input.data);
          }
        ).run();
        
        break;  // ์„ฑ๊ณต
      } catch (error) {
        logger.warn(`Attempt ${i + 1} failed`, { error });
        
        if (i < 2) {
          // ์žฌ์‹œ๋„ ์ „ ๋Œ€๊ธฐ
          await step.sleep("retry_delay", "5s");
        } else {
          throw error;  // ๋งˆ์ง€๋ง‰ ์‹œ๋„ ์‹คํŒจ
        }
      }
    }
  }
);
์ง€์›ํ•˜๋Š” ์‹œ๊ฐ„ ํ˜•์‹:
ํ˜•์‹์„ค๋ช…์˜ˆ์‹œ
s์ดˆ"5s" = 5์ดˆ
m๋ถ„"10m" = 10๋ถ„
h์‹œ๊ฐ„"2h" = 2์‹œ๊ฐ„
d์ผ"1d" = 1์ผ
์‹ค์ „ ํ™œ์šฉ:
  • ์žฌ์‹œ๋„ ์ง€์—ฐ: ์‹คํŒจ ํ›„ 5์ดˆ ๋Œ€๊ธฐ
  • Rate limiting: API ํ˜ธ์ถœ ๊ฐ„ 1์ดˆ ๋Œ€๊ธฐ
  • ๋ฐฐ์น˜ ๊ฐ„๊ฒฉ: 100๊ฐœ์”ฉ ์ฒ˜๋ฆฌ ํ›„ 10์ดˆ ํœด์‹
step.sleep()๋„ ํ•˜๋‚˜์˜ step์ด๋ฏ€๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค. ์žฌ์‹œ๋„ ์‹œ sleep์„ ๋‹ค์‹œ ์‹คํ–‰ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์‹ค์ „ ์˜ˆ์ œ

1. ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ

๋Œ€๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•  ๋•Œ๋Š” ์ž‘์€ ๋ฐฐ์น˜๋กœ ๋‚˜๋ˆ„์–ด ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๊ฐ ๋ฐฐ์น˜๋ฅผ ๋ณ„๋„ step์œผ๋กœ ๋งŒ๋“ค๋ฉด ์ค‘๊ฐ„์— ์‹คํŒจํ•ด๋„ ์ฒ˜๋ฆฌ๋œ ๋ฐฐ์น˜๋Š” ์žฌ์‹คํ–‰ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
export const processBatch = workflow(
  { name: "process_batch" },
  async ({ input, step, logger }) => {
    const batchSize = 100;
    const total = input.items.length;
    
    for (let i = 0; i < total; i += batchSize) {
      const batch = input.items.slice(i, i + batchSize);
      
      await step.define(
        { name: `process_batch_${i / batchSize}` },
        async () => {
          await Promise.all(
            batch.map(item => processItem(item))
          );
        }
      ).run();
      
      logger.info("Batch processed", {
        batch: i / batchSize,
        progress: `${Math.min(i + batchSize, total)}/${total}`,
      });
    }
    
    return { processed: total };
  }
);
ํ•ต์‹ฌ ํฌ์ธํŠธ:
  • 100๊ฐœ์”ฉ ๋ฐฐ์น˜๋กœ ๋‚˜๋ˆ„์–ด ์ฒ˜๋ฆฌ
  • ๊ฐ ๋ฐฐ์น˜๊ฐ€ ๋…๋ฆฝ์ ์ธ step
  • ์ง„ํ–‰๋ฅ ์„ ๋กœ๊ทธ๋กœ ๊ธฐ๋ก

2. ์™ธ๋ถ€ API ํ˜ธ์ถœ

์™ธ๋ถ€ API ํ˜ธ์ถœ์€ ๋„คํŠธ์›Œํฌ ๋ฌธ์ œ๋กœ ์‹คํŒจํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Step์œผ๋กœ ๋‚˜๋ˆ„๋ฉด ์‹คํŒจํ•œ API ํ˜ธ์ถœ๋งŒ ์žฌ์‹œ๋„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
export const syncData = workflow(
  { name: "sync_data" },
  async ({ input, step, logger }) => {
    // Step 1: API์—์„œ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
    const externalData = await step.define(
      { name: "fetch_from_api" },
      async () => {
        const response = await fetch(
          `https://api.example.com/data?userId=${input.userId}`
        );
        return await response.json();
      }
    ).run();
    
    // Step 2: ๋ฐ์ดํ„ฐ ์ •๊ทœํ™”
    const normalized = await step.define(
      { name: "normalize_data" },
      async () => {
        return normalizeData(externalData);
      }
    ).run();
    
    // Step 3: DB์— ์ €์žฅ
    await step.get(
      { name: "save_to_db" },
      DataModel,
      "saveMany"
    ).run(normalized);
    
    logger.info("Sync completed", { count: normalized.length });
  }
);
์žฅ์ :
  • API ํ˜ธ์ถœ ์‹คํŒจ ์‹œ ๋ฐ์ดํ„ฐ ์ •๊ทœํ™”๋Š” ์Šคํ‚ต
  • ์ •๊ทœํ™” ์‹คํŒจ ์‹œ API ์žฌํ˜ธ์ถœ ์•ˆํ•จ
  • ๊ฐ ๋‹จ๊ณ„์˜ ์‹คํ–‰ ์‹œ๊ฐ„ ์ธก์ • ๊ฐ€๋Šฅ

3. ํŒŒ์ผ ์ฒ˜๋ฆฌ ํŒŒ์ดํ”„๋ผ์ธ

ํŒŒ์ผ์„ ๋‹ค์šด๋กœ๋“œํ•˜๊ณ  ํŒŒ์‹ฑํ•˜์—ฌ ์ €์žฅํ•˜๋Š” ํŒŒ์ดํ”„๋ผ์ธ์ž…๋‹ˆ๋‹ค. ๊ฐ ๋‹จ๊ณ„๋ฅผ step์œผ๋กœ ๋‚˜๋ˆ„๋ฉด ๋””๋ฒ„๊น…๊ณผ ์žฌ์‹œ๋„๊ฐ€ ์‰ฌ์›Œ์ง‘๋‹ˆ๋‹ค.
export const processUploadedFile = workflow(
  { name: "process_uploaded_file" },
  async ({ input, step, logger }) => {
    // Step 1: ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ
    const fileContent = await step.define(
      { name: "download_file" },
      async () => {
        return await downloadFromS3(input.fileKey);
      }
    ).run();
    
    // Step 2: ํŒŒ์‹ฑ
    const parsed = await step.define(
      { name: "parse_file" },
      async () => {
        return parseCSV(fileContent);
      }
    ).run();
    
    // Step 3: ๊ฒ€์ฆ
    const validated = await step.define(
      { name: "validate" },
      async () => {
        return validateData(parsed);
      }
    ).run();
    
    // Step 4: DB์— ์ €์žฅ
    await step.define(
      { name: "save_to_db" },
      async () => {
        await bulkInsert(validated);
      }
    ).run();
    
    return { imported: validated.length };
  }
);
ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์กฐ:
  1. ๋‹ค์šด๋กœ๋“œ โ†’ 2. ํŒŒ์‹ฑ โ†’ 3. ๊ฒ€์ฆ โ†’ 4. ์ €์žฅ
  2. ๊ฐ ๋‹จ๊ณ„๊ฐ€ ์‹คํŒจํ•˜๋ฉด ํ•ด๋‹น step๋ถ€ํ„ฐ ์žฌ์‹œ๋„
  3. ํŒŒ์ผ์€ ํ•œ ๋ฒˆ๋งŒ ๋‹ค์šด๋กœ๋“œ

4. Rate Limiting์ด ์žˆ๋Š” API

API์— rate limit์ด ์žˆ์„ ๋•Œ๋Š” ์š”์ฒญ ๊ฐ„ ์ง€์—ฐ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. step.sleep()์„ ์‚ฌ์šฉํ•˜๋ฉด ์žฌ์‹œ๋„ ์‹œ ์ง€์—ฐ์ด ๋ฐ˜๋ณต๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
export const sendNotifications = workflow(
  { name: "send_notifications" },
  async ({ input, step, logger }) => {
    const users = input.userIds;
    
    for (const userId of users) {
      // ๊ฐ ์‚ฌ์šฉ์ž์—๊ฒŒ ์•Œ๋ฆผ
      await step.define(
        { name: `notify_user_${userId}` },
        async () => {
          const user = await UserModel.findById(userId);
          await sendPushNotification(user.deviceToken, input.message);
        }
      ).run();
      
      // ์‚ฌ์šฉ์ž ๊ฐ„ ๋”œ๋ ˆ์ด (API ์ œํ•œ ํšŒํ”ผ)
      if (userId !== users[users.length - 1]) {
        await step.sleep("rate_limit_delay", "100ms");
      }
    }
    
    return { sent: users.length };
  }
);
Rate limiting ์ „๋žต:
  • ๊ฐ ์•Œ๋ฆผ ์‚ฌ์ด์— 100ms ๋Œ€๊ธฐ
  • API ์„œ๋ฒ„ ๊ณผ๋ถ€ํ•˜ ๋ฐฉ์ง€
  • ์žฌ์‹œ๋„ ์‹œ ์ด๋ฏธ ์„ฑ๊ณตํ•œ ์•Œ๋ฆผ์€ ์Šคํ‚ต

Step ๋ช…๋ช… ๊ทœ์น™

๊ณ ์œ ํ•œ ์ด๋ฆ„ ์‚ฌ์šฉ

๊ฐ™์€ workflow ๋‚ด์—์„œ step ์ด๋ฆ„์€ ๊ณ ์œ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ค‘๋ณต๋œ ์ด๋ฆ„์„ ์‚ฌ์šฉํ•˜๋ฉด ๋งˆ์ง€๋ง‰ step๋งŒ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
// โŒ ๊ฐ™์€ ์ด๋ฆ„ (๋งˆ์ง€๋ง‰ ๊ฒƒ๋งŒ ์‹คํ–‰๋จ)
await step.define({ name: "process" }, async () => { ... }).run();
await step.define({ name: "process" }, async () => { ... }).run();

// โœ… ๋‹ค๋ฅธ ์ด๋ฆ„
await step.define({ name: "process_payment" }, async () => { ... }).run();
await step.define({ name: "process_shipping" }, async () => { ... }).run();

๋ฐ˜๋ณต๋ฌธ์—์„œ ๋™์  ์ด๋ฆ„

๋ฐ˜๋ณต๋ฌธ์—์„œ step์„ ์ƒ์„ฑํ•  ๋•Œ๋Š” ์ธ๋ฑ์Šค๋ฅผ ํฌํ•จํ•˜์—ฌ ์ด๋ฆ„์„ ๊ณ ์œ ํ•˜๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
// ๋ฐ˜๋ณต๋ฌธ์—์„œ
for (let i = 0; i < items.length; i++) {
  await step.define(
    { name: `process_item_${i}` },  // ๊ฐ step ๋‹ค๋ฅธ ์ด๋ฆ„
    async () => {
      await processItem(items[i]);
    }
  ).run();
}
๋ช…๋ช… ํŒ:
  • ์ž‘์—… ๋‚ด์šฉ์„ ๋‚˜ํƒ€๋‚ด๋Š” ๋ช…์‚ฌ ์‚ฌ์šฉ: fetch_user, send_email
  • ์ธ๋ฑ์Šค ํฌํ•จ: process_batch_0, process_batch_1
  • Snake case ๊ถŒ์žฅ: send_welcome_email

์ฃผ์˜์‚ฌํ•ญ

Step ์‚ฌ์šฉ ์‹œ ์ฃผ์˜์‚ฌํ•ญ:
  1. ๊ณ ์œ ํ•œ ์ด๋ฆ„: Step ์ด๋ฆ„์€ workflow ๋‚ด์—์„œ ๊ณ ์œ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    { name: "step_1" }  // โœ…
    { name: "step_1" }  // โŒ ์ค‘๋ณต
    
  2. ์‹คํ–‰ ์ˆœ์„œ: Step์€ ์ฝ”๋“œ์— ์ž‘์„ฑ๋œ ์ˆœ์„œ๋Œ€๋กœ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
    await step.define({ name: "first" }, ...).run();
    await step.define({ name: "second" }, ...).run();
    
  3. ์—๋Ÿฌ ์ „ํŒŒ: Step์ด ์‹คํŒจํ•˜๋ฉด workflow๊ฐ€ ์ค‘๋‹จ๋ฉ๋‹ˆ๋‹ค. ์„ ํƒ์  ์ž‘์—…์€ try-catch๋กœ ๊ฐ์‹ธ์„ธ์š”.
    try {
      await step.define({ name: "optional" }, ...).run();
    } catch (error) {
      logger.warn("Optional step failed", { error });
    }
    
  4. ํƒ€์ž… ์ถ”๋ก : TypeScript๋Š” step์˜ ๋ฐ˜ํ™˜ ํƒ€์ž…์„ ์ž๋™์œผ๋กœ ์ถ”๋ก ํ•ฉ๋‹ˆ๋‹ค.
    const data: UserData = await step.define(...).run();
    
  5. Sleep ์‹œ๊ฐ„ ์ œํ•œ: ๋„ˆ๋ฌด ๊ธด sleep์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์„ ์œ ์ง€ํ•˜๋ฏ€๋กœ ๊ถŒ์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
    await step.sleep("delay", "1h");   // OK
    await step.sleep("delay", "30d");  // ๋น„์ถ”์ฒœ
    

๋‹ค์Œ ๋‹จ๊ณ„