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.