Advanced16 min

Saga

Coordinate a multi-service transaction with compensations instead of a distributed lock.

By the end of this lesson, model a cross-service workflow as a saga with compensating actions.

How deep?
How the pieces actually move.

A business process spanning services — place order, reserve inventory, charge payment, schedule shipping — can't use a single ACID transaction across databases. A saga models it as a sequence of local transactions, each with a compensating action that undoes it if a later step fails. There's no global lock; you get eventual consistency with explicit rollback logic.

First, the whole system

First, see the whole system. Then we’ll open it up.

Steps and compensations

Steps and compensations

  1. Steps run forward: reserve inventory, charge payment, schedule shipping.
  2. If a step fails, compensations run in reverse: refund payment, release inventory.
flowchart LR
  s1[Reserve inventory] --> s2[Charge payment]
  s2 --> s3[Schedule shipping]
  s3 -.fail.-> c2[Refund payment]
  c2 --> c1[Release inventory]

Two coordination styles: orchestration (a central coordinator tells each service what to do next — easier to follow and monitor, but a central component) and choreography (services react to each other's events — decoupled, but the workflow is emergent and harder to trace). Every step and compensation must be idempotent, since steps are retried.

Abstraction leak

This is where the abstraction starts leaking.

A saga is not a rollback. Once you've charged a card and sent a confirmation email, you can't pretend it didn't happen — you *compensate* (refund, send a correction). Some effects aren't cleanly reversible, which is why sagas force you to think about real-world compensations, not database undo.

Consistency

Sagas give you cross-service workflows without distributed locks (better availability and autonomy) at the cost of only eventual consistency and hand-written compensation logic. A distributed 2PC transaction gives stronger consistency but poor availability and tight coupling.

How does a saga 'undo' a completed step?

With a compensating action that semantically reverses it (e.g., refund a charge) — not a database rollback, since the step already committed.

Sagas emit and react to many events. When reads and writes have very different shapes, how do you model them separately?

Next: CQRS →