Ordering Matters: Outbox vs Saga for Event‑Driven Consistency
The Gotcha
📦 Outbox guarantees in‑order event delivery by writing to a local table inside the same transaction, while Sagas can produce out‑of‑order events because each step runs in a separate service.
🎯 System Scenario & Requirements
System Goal
📦 Outbox guarantees in‑order event delivery by writing to a local table inside the same transaction, while Sagas can produce out‑of‑order events because each step runs in a separate service.
Actors & Components
- Account Service
- Outbox Publisher
- Alert Service
- Update Balance + Write Outbox
- Read & Publish Events
- Write Alert
Functional Requirements
- TLDR: 📦 Outbox guarantees in‑order event delivery by writing to a local table inside the same transaction, while Sagas can produce out‑of‑order events because each step runs in a separate service.
- Order matters when downstream consumers rely on the sequence of state changes (e.g., inventory update → email notification).
- You’re building a micro‑service that debits a user’s account and then publishes a “BalanceUpdated” event.
Constraints & Assumptions
- Constraints are derived from the rebuilt scenario stages.
⚡ Execution Stages & Decision Trace
Stage 1 — The Solution & Trade‑offs
If you need ordering, stick with Outbox or add explicit sequence numbers to saga messages and have the consumer enforce order.
If you need ordering, stick with Outbox or add explicit sequence numbers to saga messages and have the consumer enforce order.
Deep Dive
Scenario context
📦 Outbox guarantees in‑order event delivery by writing to a local table inside the same transaction, while Sagas can produce out‑of‑order events because each step runs in a separate service. Order matters when downstream consumers rely on the sequence of state changes (e.g., inventory update → email notification). You’re building a micro‑service that debits a user’s account and then publishes a “BalanceUpdated” e…
This walkthrough focuses on The Gotcha.
Before making a design choice, clarify:
- You’re building a micro‑service that debits a user’s account and then publishes a “BalanceUpdated” event.
- A downstream analytics service expects the debit event to arrive before any “BalanceLowAlert” event.
- If you use a Saga, the “BalanceUpdated” event may be published from Service A, but the “BalanceLowAlert” event could be emitted by Service B later in the same saga chain, breaking the expected order.
Concepts in play: event-driven-architecture, transaction-isolation-distributed-acid, consistency-replication, jvm-internals-and-garbage-collection
Stage 1 — The Solution & Trade‑offs
- If you need ordering, stick with Outbox or add explicit sequence numbers to saga messages and have the consumer enforce order.
Observable outcome: If you need ordering, stick with Outbox or add explicit sequence numbers to saga messages and have the consumer enforce order.
Stage 2 — Outbox Architecture Kit
- { "version": 1, "title": "Outbox vs Saga Ordering", "description": "Side‑by‑side flow of event ordering in Outbox and Saga", "view": "data-flow", "pattern": "reactive-pull", "direction": "LR", "groups": [ { "id": "servi…
Observable outcome: { "version": 1, "title": "Outbox vs Saga Ordering", "description": "Side‑by‑side flow of event ordering in Outbox and Saga", "view": "data-flow", "pattern": "reactive-pull", "direction": "LR", "groups": [ { "id": "servi…
Stage 3 — Quick Code Hook
- // Saga step with sequence number public void sendAlert(Long userId, int seq) { AlertEvent evt = new AlertEvent(userId, "LOW BALANCE", seq); eventBus.publish(evt); // downstream must buffer and sort by seq }
// Saga step with sequence number
public void sendAlert(Long userId, int seq) {
AlertEvent evt = new AlertEvent(userId, "LOW_BALANCE", seq);
eventBus.publish(evt); // downstream must buffer and sort by seq
}
Observable outcome: // Saga step with sequence number public void sendAlert(Long userId, int seq) { AlertEvent evt = new AlertEvent(userId, "LOW BALANCE", seq); eventBus.publish(evt); // downstream must buffer and sort by seq }
End-to-end outcome
After working through the stages, you should be able to explain:
- If your domain logic or consumers depend on the precise order of events, the Outbox pattern is the safer bet.
- Use a Saga only when you can tolerate out‑of‑order events or when compensations are essential.
Alternatives considered
The source focuses on one primary design. A second architecture is not compared, so no alternative is invented here.
Failure and degraded modes
The source does not isolate a separate failure mode. Limits that matter for the scenario are called out in the stage decisions above.
Key learnings
- If your domain logic or consumers depend on the precise order of events, the Outbox pattern is the safer bet.
- Use a Saga only when you can tolerate out‑of‑order events or when compensations are essential.
- If you need ordering, stick with Outbox or add explicit sequence numbers to saga messages and have the consumer enforce order.
- { "version": 1, "title": "Outbox vs Saga Ordering", "description": "Side‑by‑side flow of event ordering in Outbox and Saga", "view": "data-flow", "pattern": "reactive-pull", "direction": "LR", "groups": [ { "id": "servi…
- // Saga step with sequence number public void sendAlert(Long userId, int seq) { AlertEvent evt = new AlertEvent(userId, "LOW BALANCE", seq); eventBus.publish(evt); // downstream must buffer and sort by seq }
