Saga
Saga pattern: long-running distributed transactions via local commits + compensations. The shape behind reliable microservices.
Design patterns — Saga
EXAMPLE
// ===== The problem =====
// Multi-step business workflows across services:
// 1. Reserve inventory
// 2. Charge card
// 3. Ship order
// 4. Send email
// If any step fails, you cannot use a single ACID transaction (they cross services).
// Solution: SAGA = each step has a COMPENSATING action.
// ===== Two flavours =====
// Orchestration: a central coordinator runs the saga + invokes services in order
// Choreography: each service emits events; others react. No coordinator.
// ===== Worked example (orchestration) =====
class OrderSaga {
async execute(order) {
const steps = [
{ do: () => this.reserveInventory(order), undo: () => this.releaseInventory(order) },
{ do: () => this.chargeCard(order), undo: () => this.refundCard(order) },
{ do: () => this.ship(order), undo: () => this.cancelShip(order) },
{ do: () => this.email(order), undo: () => null },
];
const completed = [];
try {
for (const step of steps) {
await step.do();
completed.push(step);
}
return { ok: true };
} catch (err) {
// Compensate in REVERSE order
for (const step of completed.reverse()) {
try { await step.undo(); }
catch (e) { console.error('compensation failed', e); }
}
return { ok: false, error: err };
}
}
async reserveInventory(o) { /* call inventory service */ }
async releaseInventory(o) { /* compensation */ }
// ... etc
}
// ===== Persistence + recovery =====
// Sagas can crash mid-execution. To resume:
// - Persist saga state to DB at each step
// - On startup, find incomplete sagas + continue / compensate
// - State machine libraries: Temporal, Camunda, AWS Step Functions
// ===== Choreography example =====
// Service A: emits 'OrderPlaced' event
// Service B (inventory): subscribes; reserves; emits 'InventoryReserved' or 'InventoryFailed'
// Service C (payment): subscribes to InventoryReserved; charges; emits PaymentSucceeded or PaymentFailed
// Service A: subscribes to all; on any 'Failed', emits 'OrderCancelled'
// Compensation: each service reacts to OrderCancelled
// ===== Trade-offs =====
// Orchestration:
// + Clear flow visible in one place
// + Easier to debug + monitor
// - Coordinator is SPOF unless HA
// Choreography:
// + No single coordinator; truly distributed
// + Services autonomous
// - Hard to trace; emergent behaviour
// - Hard to know overall progress
// ===== When sagas win =====
// - Distributed business workflows
// - Long-running processes (hours, days)
// - Cross-service consistency without 2-Phase Commit
// ===== When NOT to use =====
// - Single-service transactions (just use ACID)
// - Workflows < 1 second (try sync chained calls)
// - Without idempotency on every step (sagas retry)
// ===== Idempotency is non-negotiable =====
// Each saga step must be safe to retry:
// - Reserve inventory with idempotency_key = order_id
// - Charge card with the same Stripe idempotency_key
// - Ship with retries-safe shipping_id
// ===== Patterns =====
// - Orchestration for clarity; choreography for autonomy
// - Persist saga state; resume on restart
// - Idempotent steps + compensations
// - Use Temporal or AWS Step Functions for managed orchestration
// - Out-of-order events: design for eventual consistency
// ===== Pitfalls =====
// - Failed compensations leaving stuck state -> alert + manual playbook
// - Retries without idempotency -> double-charge customers
// - Choreography without an event audit log -> impossible to debug
// - Trying to use sagas where 2PC or eventual consistency would do
Why it matters
Sagas split long-running distributed transactions into local commits + compensations. Orchestration is clearer; choreography is more decoupled. Pair every step with an idempotency key, persist saga state, and reach for Temporal / Step Functions / Camunda when the saga gets complex.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// Long-lived business process orchestrated as a series of compensating steps. // If any step fails, run compensations for the steps that succeeded.Try it Yourself »
Discussion
Loading…