Agent Wranglers

ARC-91Recommended

Run a change across services as a saga with undo steps

When one business action changes data owned by two or more services, run it as a saga. Each step commits locally, and each step that can fail later has an undo step that runs on failure.

How to do it

One database transaction cannot span services that own their own data. Split the action into local steps, such as reserving stock, charging the card and booking shipping. Give every step before the point of no return a compensating step, such as releasing the stock or refunding the card. Steps after that point must be safe to retry until they succeed. Either one coordinator runs the steps, or each service reacts to the event before it. Make every step and undo step idempotent, as Make receivers idempotent says. Keep the saga's state in storage so a restart picks it up. Inside one service, a local transaction and the outbox are simpler.

Examples

TypeScript example

// Orchestrated saga: undo the finished steps in reverse order on failure
const steps = [
  { run: reserveStock, undo: releaseStock },
  { run: chargeCard, undo: refundCard },
  { run: bookShipping, undo: cancelShipping },
];

async function placeOrder(order: Order) {
  const done: typeof steps = [];
  try {
    for (const step of steps) { await step.run(order); done.push(step); }
  } catch (error) {
    for (const step of done.reverse()) await step.undo(order);
    throw error;
  }
}

Python example

# Orchestrated saga: undo the finished steps in reverse order on failure
STEPS = [(reserve_stock, release_stock), (charge_card, refund_card), (book_shipping, cancel_shipping)]

async def place_order(order: Order) -> None:
    done = []
    try:
        for run, undo in STEPS:
            await run(order)
            done.append(undo)
    except Exception:
        for undo in reversed(done):
            await undo(order)
        raise

How to check

A test fails each step in turn and finds every earlier step undone, such as the stock released and the card refunded. A second test restarts the coordinator mid-saga and finds the saga finished.

Sources

For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/architecture/system-architecture/sagas">Agent Wranglers clean code and architecture playbook</a>. In code you write, add a comment next to it with the same link.