Why architecture matters here
Saga architecture matters because multi-service business transactions are unavoidable in microservices architectures. Order placement touches inventory, payment, and shipping. If payment fails after inventory reserved, the reservation must be released. Without Sagas, this coordination is ad-hoc and buggy.
Cost is small — Sagas add complexity but only where multi-service consistency matters. Overhead is proportional to the number of steps.
Reliability depends on compensation design. Every step needs a matching compensation; every compensation must handle all the ways the step might have partially succeeded. Getting this wrong is where most Saga implementations fail.
The architecture: every piece explained
Walk the diagram top to bottom.
Business Transaction. A logical unit — place order, provision account, cancel subscription — that spans multiple services.
Saga Coordinator. The mechanism that drives the transaction forward and rolls back on failure. Two patterns: choreography (event-driven) or orchestration (central controller).
Steps + Compensations. For each step S there is a compensation C. If S ran successfully but a later step fails, C runs to undo S's effect.
Choreography. Each service listens for events, executes its step, and publishes the next event. No central controller. Loose coupling but hard to reason about globally.
Orchestration. A central orchestrator (Temporal, Netflix Conductor, custom) calls each service in sequence. Easier to reason about; tighter coupling.
Step Execution. Each step is a local transaction in one service. Commits or rolls back atomically within that service. Success emits event or returns to orchestrator.
Compensation on Fail. If a later step fails, run compensations for all previously-succeeded steps in reverse order.
Persistent State. The saga's state (which steps done, which pending, current data) is persisted so a crash doesn't lose progress.
Idempotency + Retries. Each step (and each compensation) must be idempotent. Retries are inevitable; must be safe.
Observability. Distributed tracing shows the whole saga across services. Essential for debugging why a specific saga failed.
End-to-end saga flow
Trace an orchestrated saga: place an order. Client calls the order service; the service starts an orchestrator saga.
Step 1: OrderService.createOrder(). Records order in pending state. Returns order ID.
Step 2: InventoryService.reserve(order_id, items). Reserves stock. Success.
Step 3: PaymentService.charge(order_id, amount). Payment declined.
Compensations trigger in reverse: InventoryService.release(order_id) — undo reservation. OrderService.cancel(order_id) — mark cancelled with reason.
Client receives failure with reason "payment declined." Order remains cancelled, inventory available. No inconsistency.
Trace the same in choreography: OrderService.createOrder() commits and emits OrderCreated event. InventoryService listens, reserves, emits ItemsReserved. PaymentService listens, tries to charge, emits PaymentFailed. InventoryService listens for PaymentFailed for this order, releases; emits ItemsReleased. OrderService listens for ItemsReleased, cancels order.
Both work; choose based on team preferences and tooling.