ClaudeCloudflare2026/06/25 13:00

How we built saga rollbacks for Cloudflare Workflows

要点だけを先に読めるように短く再構成したセクションです。

元記事

Quick Digest

要約

要点だけを先に読めるように短く再構成したセクションです。

claudeja

How we built saga rollbacks for Cloudflare Workflows の要約

Key Points

  • ポイント1: How we built saga rollbacks for Cloudflare Workflows 2026-06-25 Vaishnav Kavitha Mia Malden André Venceslau 9 min read Cloudflare Workflows allows you to build durable, multi-step
  • ポイント2: When a Workflow executes, each step can call external systems, retry failures, and persist state across restarts.
  • ポイント3: But if one step fails, it may leave earlier work from completed steps in an inconsistent or partial state.

Summary

この記事は 2026-06-25 に公開された「How we built saga rollbacks for Cloudflare Workflows」の内容を日本語で簡潔にまとめたものです。

Key Points

  • ポイント1: How we built saga rollbacks for Cloudflare Workflows 2026-06-25 Vaishnav Kavitha Mia Malden André Venceslau 9 min read Cloudflare Workflows allows you to build durable, multi-step
  • ポイント2: When a Workflow executes, each step can call external systems, retry failures, and persist state across restarts.
  • ポイント3: But if one step fails, it may leave earlier work from completed steps in an inconsistent or partial state.

Full Translation

翻訳

原文の流れを保ったまま読める翻訳セクションです。

claudeja

How we built saga rollbacks for Cloudflare Workflows(原文タイトル)

概要

公開日: 2026-06-25 翻訳生成に失敗したため、原文をそのまま保存しています。

原文

How we built saga rollbacks for Cloudflare Workflows 2026-06-25 Vaishnav Kavitha Mia Malden André Venceslau 9 min read Cloudflare Workflows allows you to build durable, multi-step applications with built-in retries and state persistence across long-running processes. When a Workflow executes, each step can call external systems, retry failures, and persist state across restarts. But if one step fails, it may leave earlier work from completed steps in an inconsistent or partial state. Today we’re shipping saga rollbacks for Workflows, allowing you to declare rollback logic within the step itself, in case of failure. For example, consider a workflow for transferring funds between accounts at two different banks: Debit from account at Bank A Credit to account at Bank B Send email confirmation to both account owners What happens if Step 2, the credit to account at Bank B, fails? Once the debit succeeds at Bank A, the transaction is committed and the money has left its system. As the orchestrator of the transaction, you cannot simply “undo” the operation in Bank A's system. Instead, the money must be credited back to the account at Bank A through a new operation that semantically reverses the first one. This pairing of an operation and its compensation logic is called the saga pattern . Before today, developers had to implement their own compensation logic to track what succeeded, what failed, and what actions should be taken upon failure, outside of the steps’ direct definitions. Now, you can define compensation logic for each step.do() as an argument within the steps themselves, maintaining your workflow’s durability for the rollback as well. // track what completed so we know what to undo let debitA; let creditB; try { debitA = await step.do("debit-bank-a", () => bankA.debit(from, amount)); creditB = await step.do("credit-bank-b", () => bankB.credit(to, amount)); await step.do("notify", () => notifyBoth(from, to, amount)); } catch (error) { // unwind in reverse. each undo is its own durable step, // must be idempotent, and must keep going if one fails. if (creditB) { try { await step.do("reverse-credit-b", () => bankB.debit(to, amount, creditB.id)); } catch (e) { await alertOnCall("reverse-credit-b failed", e); } } if (debitA) { try { await step.do("refund-debit-a", () => bankA.credit(from, amount, debitA.id)); } catch (e) { await alertOnCall("refund-debit-a failed", e); } } throw error; } Without rollbacks // each step ships with its own undo. add a step, // add its rollback right here. no growing catch // block, no manual ordering, no replay logic. await step.do("debit-bank-a", () => bankA.debit(from, amount), { rollback: async ({ output }) => bankA.credit(from, amount, output.id), }); await step.do("credit-bank-b", () => bankB.credit(to, amount), { rollback: async ({ output }) => bankB.debit(to, amount, output.id), }); await step.do("notify", () => notifyBoth(from, to, amount)); With rollbacks Try it out To use rollbacks, just pass an options object containing a rollback function as the last argument to step.do() . const debit = await step.do( "debit-account-a", async () => { return await bankA.debit({ accountId: fromAccountId, amount, idempotencyKey: ${transferId}:debit-account-a, }); }, { rollback: async () => { await bankA.credit({ accountId: fromAccountId, amount, idempotencyKey: ${transferId}:rollback-debit-account-a, }); }, } ); // The idempotency keys make both the forward operations and rollback operations safe to retry without duplicating the transfer const credit = await step.do( "credit-account-b", async () => { return await bankB.credit({ accountId: toAccountId, amount, idempotencyKey: ${transferId}:credit-account-b, }); }, { rollback: async ({ output }) => { if (output === undefined) { return; } await bankB.debit({ accountId: toAccountId, amoun