Unravel โบ Java concepts โบ Design patterns and architecture
Sagas and distributed transactions
A business process that spans several services cannot use one database transaction.
Learn these first
At a glance
Booking a holiday from separate companies: flight, hotel, car.
- 1Each service performs a local transaction and then triggers the next step (by an event or a command)A failure triggers compensations in reverse order: refund the payment, release the stock, cancel the order.
- 2Choreography: services react to each other events with no central coordinatorSimple for two or three steps, hard to follow for many.
- 3Orchestration: one coordinator (a saga class, a workflow engine such as Temporal or Camunda, or Axon sagas) keeps the state and sends commandsIt is easier to see, test and monitor.
- 4Each step and each compensation must be idempotent and retryable, because messages repeat and the coordinator may restart
- There is no isolation between steps: other requests can see half-finished state (a reserved but unpaid order)
- Some actions cannot be undone (an email sent, a parcel shipped)
- A saga log (database table or event store) persists the current step and data
@Sagapublic class OrderSaga { @StartSaga @SagaEventHandler(associationProperty = "orderId") public void on(OrderPlaced event, CommandGateway commands) { commands.send(new ReserveStock(event.orderId(), event.items())); } @SagaEventHandler(associationProperty = "orderId") public void on(StockRejected event, CommandGateway commands) { commands.send(new CancelOrder(event.orderId(), "out of stock")); }}- line 1 uses Saga, the core of Sagas and distributed transactions
- line 2 uses Saga, the core of Sagas and distributed transactions
- line 3 uses StartSaga, the core of Sagas and distributed transactions
- line 4 uses Saga, the core of Sagas and distributed transactions
- line 9 uses Saga, the core of Sagas and distributed transactions
Read the full explanation (about 2 min)
A business process that spans several services cannot use one database transaction. A saga splits it into local steps, and each step that already succeeded has a compensating action to undo it if a later step fails.
A mental model
Booking a holiday from separate companies: flight, hotel, car. If the car company refuses, you cancel the hotel and the flight one by one, instead of one magic undo that covers everyone.
How it works
Each service performs a local transaction and then triggers the next step (by an event or a command). A failure triggers compensations in reverse order: refund the payment, release the stock, cancel the order.
Choreography: services react to each other events with no central coordinator. Simple for two or three steps, hard to follow for many.
Orchestration: one coordinator (a saga class, a workflow engine such as Temporal or Camunda, or Axon sagas) keeps the state and sends commands. It is easier to see, test and monitor.
Each step and each compensation must be idempotent and retryable, because messages repeat and the coordinator may restart.
Going deeper
There is no isolation between steps: other requests can see half-finished state (a reserved but unpaid order). Use semantic locks (a PENDING status) and design screens and queries for intermediate states.
Some actions cannot be undone (an email sent, a parcel shipped). Order steps so that irreversible ones come last, and use "compensation" such as an apology or a return label where reversal is impossible.
Timeouts are part of the design: a saga that waits forever for a reply holds the process hostage. Define deadlines and what to do when they pass.
Under the hood
A saga log (database table or event store) persists the current step and data. On restart the coordinator loads unfinished sagas and continues or compensates. Workflow engines replay a deterministic workflow function from its event history to rebuild state, which is why workflow code must avoid randomness and wall-clock reads.
Common mistakes
- Compensations that are not idempotent.
- Ignoring intermediate states in the user interface.
- No timeout handling.
- Using a distributed (two-phase) transaction across services instead of a saga without understanding the cost.
How Unravel recognises it
When you paste code, Unravel looks for these signs of sagas and distributed transactions. Spotting them yourself is a good habit when you read unfamiliar code:
- Annotations:
@Saga@SagaEventHandler@StartSaga@EndSaga@WorkflowInterface@ActivityInterface@WorkflowMethod@ActivityMethod@JavaDelegate - Types:
SagaOrchestratorSagaManagerSagaStepSagaStateCompensatingActionSagaBuilderSagaWorkflowClientWorkflowOptionsActivityOptionsJavaDelegate - Method calls:
compensate()addCompensation()Workflow.newActivityStub()
Part of a learning path
- Reliable messaging and distributed patterns ยท previous: Transactional outbox pattern ยท next: CQRS and event sourcing
Related concepts
Frequently asked questions
What is Sagas and distributed transactions?
A business process that spans several services cannot use one database transaction. A saga splits it into local steps, and each step that already succeeded has a compensating action to undo it if a later step fails.
What are the most common mistakes with Sagas and distributed transactions?
Compensations that are not idempotent. Ignoring intermediate states in the user interface. No timeout handling. Using a distributed (two-phase) transaction across services instead of a saga without understanding the cost.
How can I see Sagas and distributed transactions in real code?
Paste your own Java into Unravel and it will point out where sagas and distributed transactions appears, explain how it works at the detail level you choose, and draw the flow of the code around it.