# How do I design eventual consistency with the Saga Pattern in a distributed system?

> Split the transaction into local steps with compensating actions, driven by one orchestrator over a queue, each idempotent and published through an outbox.

- Asked: 2026-05-27
- Answered: 2026-05-30
- Asked by: İpek
- Tags: dayaniklilik, mikroservis, queue
- Source: https://www.muhammetsafak.com.tr/en/just-ask/orchestrated-saga-for-eventual-consistency-across-services/
- Language: en-US
- Author: Muhammet Şafak

---
**Question:** In our microservice architecture the order flow goes: the Order Service takes payment, the Stock Service decrements stock, the Shipping Service creates the shipment. When shipping fails, we need to refund the payment and restock the items.

Since 2PC caused performance problems we want to use the Saga Pattern. How do I design an orchestrator-based Saga over queue mechanisms?


Short answer: avoiding 2PC is the right call — it blocks and doesn't scale. A Saga turns one atomic transaction into a **sequence of local transactions**, each with a compensating action. For your flow, an orchestrator-based Saga is the right choice.

The real issue is this: you can no longer roll everything back inside one transaction. Instead, you have to enforce the discipline of "if a step breaks, compensate the steps that came before it."

1. **Let a central coordinator drive the steps over a queue.** The orchestrator (Order Service) pushes commands to the queue: reserve payment → decrement stock → create shipment. Each service reports back when it finishes its work, and the coordinator moves to the next step. The flow lives in one place — explicit and readable.
2. **On failure, run compensations in reverse.** If the shipping step blows up, the orchestrator walks backwards: restock the items, refund the payment. Compensating operations aren't an "undo," they are **forward-direction balancing** operations — a refund is a real transaction, not a magic rollback.
3. **Make every step idempotent.** Retries are guaranteed on queues; the same "decrement stock" command can arrive twice. Put a deterministic `saga_id` + `step_id` on each command and skip it if already processed. Without idempotency, a saga produces double refunds / double stock on the second attempt.
4. **Preserve atomicity with the outbox pattern.** The pair "do local work + publish the next event" must happen in one transaction. Write the work and the event to an `outbox` table in the same DB, and let a separate relay move it to the queue. Otherwise the work commits but the event doesn't publish, and the saga is left stranded.
5. **Persist the saga state.** Which step you're on, which ones completed — keep it in a table so that even if the coordinator crashes, it resumes where it left off when it comes back up.

**Bottom line:** prefer orchestration over choreography; the flow is explicit and the compensation ordering is clear. Accept eventual consistency: stock may look inconsistent for a few seconds, then reconcile. I go deeper on the architecture on sade.dev and on the queue mechanics in the hub post.

## Related Reading

- [Async Job Processing with Laravel Queue and Supervisor](/en/blog/async-job-processing-with-laravel-queue-and-supervisor/) — Blog
- [When I Move to Microservices](https://sade.dev/en/journal/when-i-move-to-microservices) — sade.dev
- [How do I handle poison-pill messages and a Dead Letter Queue in the queue?](https://www.muhammetsafak.com.tr/en/just-ask/poison-pill-messages-and-dead-letter-queues-in-laravel/) — Just Ask
- [How should I carry a correlation ID from the HTTP request all the way into queued jobs?](https://www.muhammetsafak.com.tr/en/just-ask/how-should-i-carry-a-correlation-id-from-the-http-request/) — Just Ask
- [How do I design a self-healing deploy that auto-rolls back when the error rate spikes?](https://www.muhammetsafak.com.tr/en/just-ask/automated-rollback-on-error-spikes-self-healing-deploys/) — Just Ask
