Saga Pattern
how to "undo" across microservices that don't share a database
System designsystem design / distributed systems / microservices
The problem
Booking a trip touches 3 services: Payment, Flight, Hotel. In a monolith you'd wrap all three in ONE database transaction — they commit or roll back together. But each microservice owns its own database. There is no shared transaction. So what happens when Payment succeeds, Flight succeeds, and Hotel is sold out?
You can't ROLLBACK the flight — it's already committed in another service's DB. That's the distributed-transaction problem.
The saga idea
A saga is a sequence of LOCAL transactions, one per service. Each step has a paired "compensating transaction" that semantically undoes it.
Step 1: Charge card ↩ Refund card
Step 2: Reserve flight ↩ Cancel flight
Step 3: Reserve hotel ↩ (last step — nothing after to undo)
If step 3 fails, run the compensations backwards: cancel flight, then refund card. No global rollback — you undo with NEW transactions.
Compensation ≠ rollback
A rollback erases history as if it never happened. A compensation is a new business action that counteracts the old one. The refund still shows in the ledger; the flight cancellation may even incur a fee. You're undoing the EFFECT, not the record. Make compensations idempotent — they will get retried.
Two ways to coordinate
- Orchestration: a central saga orchestrator tells each step what to do and fires compensations on failure. You see the whole flow in one place — but the orchestrator concentrates the complexity.
- Choreography: no coordinator. Each service emits events; the next reacts. ("PaymentCompleted" → Flight service reserves, emits "FlightReserved" → …). Decoupled, but the flow is smeared across services and hard to trace.
Rule of thumb: 2–4 steps → choreography is fine. More steps, or branching logic → orchestration.
Where it bites
- No isolation. Mid-saga, others can see the half-done state (card charged, no hotel yet). Design for it — e.g. a PENDING status — don't assume ACID isolation.
- Compensations fail too. The refund service is down. You need retries + a dead-letter queue + alerting, not just a happy-path "undo".
- Some steps can't be undone. You can't unsend a confirmation email. Order steps so irreversible actions run LAST, after everything reversible has already succeeded.
Your exercise
Sketch the saga for an e-commerce checkout: reserve inventory → charge payment → create shipment. Write the compensating action for each step. Then answer: which step is riskiest to place last, and why? For just these 3 steps — orchestration or choreography?