Saga Pattern
Saga manages distributed transactions as a sequence of local transactions with compensating actions on failure.
Introduction
Saga manages distributed transactions as a sequence of local transactions with compensating actions on failure. No 2PC across microservices — each service commits locally, publishes event, next step triggers. Failure runs compensations (cancel reservation, refund payment) to reach eventual consistency.
The story
Travel booking needed flight + hotel + car across 3 vendor APIs — 2PC impossible. Choreography saga: each service publishes events; next reacts. Failures trigger compensating transactions. Bookings completed reliably without distributed locks.
The business problem
Distributed ACID is impossible or impractical across services:
- Partial failure: flight booked, hotel fails — customer charged wrong.
- 2PC unavailable: external vendor APIs don't support XA transactions.
- Lock amplification: distributed locks don't scale across HTTP.
- Manual reconciliation: ops fixes inconsistent cross-service state.
The problem teams faced
Saga fits when:
- Business transaction spans multiple services/databases.
- Each step can commit locally but global atomicity needed eventually.
- Compensating actions exist for each step (cancel, refund, release).
- 2PC/XA not available or too slow across boundaries.
Understanding the topic
Intent: Sequence of local transactions with compensations instead of global rollback.
- Choreography — each service listens and publishes; no central coordinator.
- Orchestration — central saga manager issues commands and handles failures.
- Compensating transaction — semantic undo (CancelBooking, RefundPayment).
- Idempotency — every step and compensation safe to retry.
Internal architecture
Orchestrated travel booking saga:
class BookTripSaga {async run(trip: TripRequest) {try {const flight = await this.flights.reserve(trip.flight)const hotel = await this.hotels.reserve(trip.hotel)await this.cars.reserve(trip.car)} catch (e) {if (hotel) await this.hotels.cancel(hotel.id)if (flight) await this.flights.cancel(flight.id)throw e}}}// Each reserve() is local TX + idempotent + publishes event
Visual explanation
Three diagrams cover saga flow, compensation, and choreography vs orchestration:
Informative example
Before / after — hope-and-pray HTTP chain to saga:
// ❌ No compensation — partial booking on failureawait flightApi.book(flight)await hotelApi.book(hotel) // fails — flight still bookedawait carApi.book(car)// ✅ Saga with compensationconst sagaId = uuid()try {const f = await flightApi.reserve(sagaId, flight)const h = await hotelApi.reserve(sagaId, hotel)await carApi.reserve(sagaId, car)} catch (e) {await hotelApi.compensate(sagaId) // cancel if reservedawait flightApi.compensate(sagaId)throw new BookingFailedError(sagaId, e)}
Execution workflow
Define steps
List local transactions in order.
Real-world use
Temporal/Cadence workflows, Axon Saga, microservices order fulfillment, travel booking, e-commerce checkout across inventory/payment/shipping services.
Production case study
Travel booking 3-vendor saga:
- Challenge: flight + hotel + car; 2PC impossible across vendors.
- Decision: choreography saga with compensating cancels.
- Outcome: reliable bookings; failures cleanly compensated.
Trade-offs
- Pro: eventual consistency without 2PC; works across external APIs.
- Con: compensations are semantic — not automatic rollback.
- Con: debugging distributed saga state harder than single TX.
Decision framework
- Use when transaction spans services and 2PC unavailable.
- Orchestration when steps >5 or complex branching.
- Choreography when linear and teams own each service.
- Every step needs defined compensation or accept inconsistency.
Best practices
- Saga ID on every message — correlate steps.
- Idempotent reserve/compensate handlers — at-least-once delivery.
- Persist saga state — survive process crash mid-saga.
- Visible saga status for customer support.
Anti-patterns to avoid
- Saga without compensations — leaves orphan reservations.
- Choreography soup — 12 listeners, untraceable flow (use orchestrator).
- Assuming compensate equals undo — refund may take days.
Common mistakes
- Compensation fails — need retry and manual intervention playbook.
- Non-idempotent steps double-charge on retry.
Debugging tips
- Saga state table: sagaId, step, status, last error.
- Distributed trace with saga ID across all service calls.
Optimization strategies
- Temporal stores saga state durably — prefer for long-running sagas.
Common misconceptions
- Saga ≠ 2PC. Eventual consistency — intermediate states visible.
- Saga is Mediator/Orchestrator at workflow scale (behavioral link).
- Compensate ≠ rollback — semantic business undo.
Advanced interview questions
Interview Prep
Practice concise answers, then expand each card for the explanation.
1IntermediateQuestionSaga vs 2PC?+
Answer
Follow-up
2AdvancedQuestionChoreography vs orchestration?+
Answer
Follow-up
3AdvancedQuestionCompensation example?+
Answer
Follow-up
Summary
You can design sagas with compensations, pick choreography vs orchestration, and explain vs 2PC. Tell the travel booking story — if you can do that, you own Saga.