Event-driven saga without orchestrator: services communicate via Kafka event bus with compensating events
Saga Choreography is a design pattern for managing distributed transactions where there is no central coordinator. Instead, each service participates in the saga by listening for events, performing its local transaction, and emitting a new event for the next service in the chain. Compensation is also event-driven -- failure events cascade back through the same event bus, triggering each service to undo its work.
This approach is a natural fit for event-driven microservice architectures where services communicate through a message broker such as Apache Kafka, RabbitMQ, or Amazon EventBridge. Each service only knows about the events it consumes and produces, with no awareness of the overall saga flow.
Compare this with the Saga Orchestration pattern, where a central Orchestrator issues commands and controls the workflow. Both patterns solve the same problem -- distributed transactions without 2PC -- but make fundamentally different tradeoffs.
The backbone of the choreography. All saga events flow through Kafka topics. Each service has its own topic (e.g., orders.events, payments.events, inventory.events, shipping.events). Services publish events after completing their local transaction and subscribe to topics from upstream services.
Services form an implicit chain through their event subscriptions:
Client -> Order Service -> [orders.events]
-> Payment Service -> [payments.events]
-> Inventory Service -> [inventory.events]
-> Shipping Service -> [shipping.events]
-> Order Service (finalize)
Each service in the chain:
On failure, events flow in reverse through the bus:
Inventory Service emits InventoryFailed -> [inventory.events]
-> Payment Service consumes, refunds, emits PaymentRefunded -> [payments.events]
-> Order Service consumes, cancels order
Each service independently decides how to react to failure events from downstream services. There is no central component coordinating the compensation.
Each service owns its data store. There are no shared databases or distributed locks. Consistency is achieved through the saga event chain rather than through database-level transactions.
Services react to events rather than receiving commands. The Order Service does not tell the Payment Service to charge -- it simply emits an OrderCreated event. The Payment Service decides on its own to charge when it sees that event. This keeps services decoupled at the API level.
Each service is responsible for its own compensation logic. The Payment Service knows that when it sees an InventoryFailed event, it should refund the charge and emit PaymentRefunded. The Order Service knows that when it sees PaymentRefunded, it should cancel the order. No central component dictates these reactions.
Because there is no Orchestrator, there is no single component whose failure halts all sagas. If one service goes down, events queue up in Kafka and are processed when the service recovers. The event bus provides durability and buffering.
The saga workflow is defined implicitly by the event subscriptions across services. There is no single place that shows the full flow. To understand the complete saga, you must trace which service listens to which topic and what events it produces. This is a key tradeoff of choreography.
Saga Choreography works well for:
Consider Saga Orchestration instead when:
OrderCreated triggers PaymentCharged, which triggers InventoryReserved, which triggers ShippingScheduled. The final ShippingScheduled event flows back to the Order Service, which marks the order as confirmed.InventoryFailed, which the Payment Service consumes to issue a refund (PaymentRefunded), which the Order Service consumes to cancel the order. Compensating events cascade back through the event bus without any central coordinator directing them.