iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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…