advanced~3h

Saga Pattern

The pattern actually running in production at most real microservices companies. This is the single most system-design-interview-relevant chapter in this entire book.

A Saga replaces one impossible cross-database transaction with a sequence of local transactions, each fully committed on its own service before the next step runs. There is no global lock, no coordinator holding everyone hostage (Chapter 16 §3) — each step commits independently and immediately. Correctness across the whole sequence comes not from atomicity, but from a second mechanism: if a later step fails, previously completed steps are undone via compensating transactions that run afterward, not a real database rollback.

◆ The problem

Once Payment Service has genuinely committed "card charged," there's no database ROLLBACK that can undo it — that money has already moved. The only way to "undo" a committed real-world action is another, forward-moving action that reverses its effect.

Step (forward)Compensating transaction (backward)
Create orderCancel/delete the order
Charge paymentIssue a refund
Reserve stockRelease the reservation
Send confirmation emailCannot truly be undone — send a "sorry, order cancelled" email instead, or design the saga so the email step runs last, after everything else has already succeeded

▲ Common mistake

Not every action has a clean compensation (you can't un-send an email, un-deliver a package). Good saga design deliberately orders steps so irreversible actions happen last, after every reversible step has already succeeded — minimizing the window where an un-compensable failure could still occur.

In choreography, each service publishes an event when it finishes its step, and independently reacts to events from other services — directly reusing the event-driven pattern. There is no single service that "knows" the whole saga; the overall flow emerges from each service's local reactions.

Choreography: each service reacts to events independently. Order Service both starts the saga and listens for its own compensation trigger — nobody centrally "runs" the whole flow.

@Component public class OrderSagaListener { @KafkaListener(topics = "payment-failed") public void onPaymentFailed(PaymentFailedEvent event) { // compensation: this service reacts to someone ELSE's failure by undoing its OWN earlier step orderService.cancelOrder(event.orderId()); } }

💻 Code example

@Component public class OrderSagaListener { @KafkaListener(topics = "payment-failed") public void onPaymentFailed(PaymentFailedEvent event) { // compensation: this service reacts to someone ELSE's failure by undoing its OWN earlier step orderService.cancelOrder(event.orderId()); } }

In orchestration, a dedicated orchestrator service explicitly calls each participant in sequence and decides what to do next based on the result — the saga's entire flow logic lives in one place, readable top-to-bottom, rather than scattered as implicit event-reaction chains across every participating service.

@Service public class OrderSagaOrchestrator { public void execute(OrderRequest request) { Long orderId = orderService.createOrder(request); try { paymentService.chargeCard(request.paymentDetails()); } catch (PaymentFailedException e) { orderService.cancelOrder(orderId); // compensate step 1 return; } try { inventoryService.reserveStock(request.items()); } catch (OutOfStockException e) { paymentService.refund(orderId); // compensate step 2 orderService.cancelOrder(orderId); // compensate step 1 return; } notificationService.sendConfirmation(orderId); // irreversible step, deliberately LAST } }

💻 Code example

@Service public class OrderSagaOrchestrator { public void execute(OrderRequest request) { Long orderId = orderService.createOrder(request); try { paymentService.chargeCard(request.paymentDetails()); } catch (PaymentFailedException e) { orderService.cancelOrder(orderId); // compensate step 1 return; } try { inventoryService.reserveStock(request.items()); } catch (OutOfStockException e) { paymentService.refund(orderId); // compensate step 2 orderService.cancelOrder(orderId); // compensate step 1 return; } notificationService.sendConfirmation(orderId); // irreversible step, deliberately LAST } }
ChoreographyOrchestration
Flow visibilityImplicit — scattered across every service's event listeners; harder to see the whole pictureExplicit — the entire flow reads top-to-bottom in one orchestrator class
CouplingLoosest — services only know about events, not each otherOrchestrator knows about every participant; participants stay simpler
Best forA small number of steps, simple reactionsComplex, many-step sagas where explicit flow control and visibility matter more than minimal coupling
Common real-world choiceMost production systems past 4-5 saga steps lean orchestration — pure choreography's implicit flow becomes genuinely hard to debug and reason about as step count grows.

◆ The problem

Directly connecting back to Kafka's at-least-once delivery guarantee (covered fully in Chapter 19): a saga step listening for an event can receive that same event more than once, due to consumer restarts or rebalances. If chargeCard() isn't idempotent, a redelivered event means charging the customer's card twice for the same order.

@Transactional public void chargeCard(String orderId, PaymentDetails details) { if (paymentRepository.existsByOrderId(orderId)) { return; // already processed this exact order — safe no-op on redelivery } Payment payment = paymentGateway.charge(details); paymentRepository.save(new PaymentRecord(orderId, payment.getId())); }

💻 Code example

@Transactional public void chargeCard(String orderId, PaymentDetails details) { if (paymentRepository.existsByOrderId(orderId)) { return; // already processed this exact order — safe no-op on redelivery } Payment payment = paymentGateway.charge(details); paymentRepository.save(new PaymentRecord(orderId, payment.getId())); }

Want a visual for this concept?

Generate a diagram tailored to “Saga Pattern” — the AI picks whichever visual (flowchart, comparison, sequence, etc.) best fits.

Sign in to generate a visual →

Practice quiz

Next Step

Continue to Outbox, Inbox & CDC← Back to all Transaction Mastery chapters