Back to Docs

Recipe: Distributed saga orchestrator

Coordinate multi-step workflows across independent services with compensating rollbacks when any step fails.

Overview

A saga splits a long-lived transaction into a sequence of local transactions. Each step publishes an event; the orchestrator listens and advances the state machine. If a step fails, the orchestrator runs compensating actions in reverse order.

State machine

enum SagaState {
  PENDING,
  RESERVING_INVENTORY,
  CHARGING_PAYMENT,
  CONFIRMING,
  COMPLETED,
  COMPENSATING,
  FAILED,
}

Orchestrator loop

async function run(sagaId: string) {
  const saga = await load(sagaId);
  while (saga.state !== SagaState.COMPLETED
      && saga.state !== SagaState.FAILED) {
    const step = steps[saga.state];
    try {
      await step.forward(saga);
      saga.state = step.next;
    } catch {
      saga.state = SagaState.COMPENSATING;
      await compensate(saga, step);
      saga.state = SagaState.FAILED;
    }
    await persist(saga);
  }
}

Compensation

Each step registers a rollback handler. When compensation triggers, the orchestrator walks the completed steps in reverse and invokes their rollbacks. Idempotency keys prevent double-execution.

Idempotency

Every step carries a unique idempotency key. Receivers deduplicate by key so the orchestrator can safely retry without side effects.