Cloud · Messaging · DWG NO. 042

Saga

Run a distributed transaction as a series of local transactions, each with a compensating action, coordinated through events rather than a single commit.

TypeMessaging / Data
ScopeMultiple services
ComplexityHigh
Common inDistributed order processing, multi-service booking flows

There's no single database transaction across service boundaries

A single-database transaction can roll back atomically if one step fails. Across independent services — each with its own database — there's no such thing as one atomic commit spanning all of them. If step three of four fails, something has to actively undo steps one and two; nothing does it automatically.

Local transactions, chained by events, undone by compensation

Each service performs its own local transaction and publishes an event when it's done. That event triggers the next service's local transaction (this event-driven chaining is called choreography — see the Orchestration page for the alternative where one coordinator calls each step directly). If any step fails, previously completed steps run a compensating transaction to undo their effect.

Order SvcOrderCreatedPayment SvcPaymentFailedOrder Svc: undocancels order

A choreographed saga reacting to events

Each service only reacts to events; there's no central coordinator. Payment failing triggers a compensating cancellation, chained the same event-driven way.

saga-choreography.js
const bus = new EventTarget();

function orderService() {
  bus.addEventListener("OrderRequested", (e) => {
    console.log("order: created (pending)");
    bus.dispatchEvent(new CustomEvent("OrderCreated", { detail: e.detail }));
  });
  bus.addEventListener("PaymentFailed", (e) => {
    console.log("order: compensating — cancelling order");
  });
}

function paymentService() {
  bus.addEventListener("OrderCreated", (e) => {
    const ok = e.detail.orderId !== "bad-order";
    if (ok) {
      console.log("payment: charged");
      bus.dispatchEvent(new CustomEvent("PaymentCompleted", { detail: e.detail }));
    } else {
      console.log("payment: declined");
      bus.dispatchEvent(new CustomEvent("PaymentFailed", { detail: e.detail }));
    }
  });
}

orderService();
paymentService();

bus.dispatchEvent(new CustomEvent("OrderRequested", { detail: { orderId: "order-1" } }));
bus.dispatchEvent(new CustomEvent("OrderRequested", { detail: { orderId: "bad-order" } }));

Trade-offs