A saga keeps data consistent with a sequence of local transactions, one per service. Each step commits in its own database. Then it triggers the next step with an event or a command.
If a step fails, the saga runs compensating transactions. These undo the earlier steps in business terms. For an order, they might:
- release the reserved stock
- refund the payment
- mark the order as cancelled
Compensations are business actions, not database rollbacks. So you must design one for each step. Steps should also be idempotent: running a step twice has the same effect as running it once. This matters because the broker can deliver a message more than once.
Sagas also lack isolation. Other requests can see in-between states such as "payment pending". So you design for that with explicit states.
2PC (two-phase commit) is avoided for these reasons:
- It holds locks across services while they wait for a coordinator.
- It blocks if the coordinator fails.
- It ties the availability of all the services together.
- Many message brokers and NoSQL stores do not support it.
- A saga is a sequence of local transactions, one per service.
- Compensating transactions undo the earlier steps.
- Steps must be idempotent, and in-between states are visible.
- Sagas have no isolation, so you design explicit states.
- 2PC blocks, holds locks and ties services' availability together.