In a monolithic application, a database transaction guarantees atomicity across multiple operations. In a microservices architecture, where each service has its own database, traditional ACID transactions are no longer possible. The saga pattern provides a way to maintain data consistency across services without distributed transactions.
What Is a Saga?
A saga is a sequence of local transactions, each within a single service. Each local transaction updates the database and publishes a message or event that triggers the next local transaction. If a local transaction fails, the saga executes compensating transactions to undo the changes made by previous transactions.
There are two common implementations: choreography-based and orchestration-based sagas.
Choreography-Based Saga
In a choreography-based saga, each service knows what to do next and publishes events after completing its local transaction. There is no central coordinator.
Consider an order placement flow:
2. **Inventory Service** receives the event, reserves inventory, publishes `InventoryReserved`.
3. **Payment Service** receives the event, processes payment, publishes `PaymentProcessed`.
4. **Order Service** receives the event, updates order status to `CONFIRMED`.
If payment fails, the Payment Service publishes `PaymentFailed`. The Inventory Service listens for this event and releases the reserved inventory. The Order Service updates the order to `FAILED`.
**Pros:** Simple, no single point of failure, services are loosely coupled.
**Cons:** The flow is spread across services, making it harder to trace and debug. Services can become tightly coupled to each other's events. Complex workflows are difficult to manage.
Orchestration-Based Saga
In an orchestration-based saga, a central orchestrator (the saga coordinator) tells each service what to do. The orchestrator sends commands to services and handles their responses.
Using the same order flow:
2. Order Service creates the order and responds with `success`.
3. Orchestrator tells Inventory Service to reserve inventory.
4. Inventory Service responds with `success`.
5. Orchestrator tells Payment Service to process payment.
6. Payment Service responds with `success`.
7. Orchestrator tells Order Service to confirm the order.
If any step fails, the orchestrator executes compensating actions in reverse order. For example, if payment fails, it tells Inventory Service to release inventory and Order Service to reject the order.
**Pros:** Clear workflow definition in one place, easier to monitor and debug, good for complex workflows with conditional logic.
**Cons:** The orchestrator is a single point of failure and can become a bottleneck.
Compensating Transactions
Sagas rely on compensating transactions for rollback. Unlike ACID rollbacks, compensations are application-level operations that semantically undo a previous action. For example:
Design compensations to be idempotent. A compensation might be executed multiple times if the orchestrator crashes and retries. A good test: if the compensation runs twice, the system should still end up in the correct state.
Handling Failures and Retries
Failures in sagas fall into two categories:
**Business failures** (insufficient inventory, invalid payment) trigger compensation flows immediately. These are expected and part of normal operation.
**Technical failures** (timeout, network error) are harder to handle. Best practices include:
When to Use Sagas
Use sagas when:
Do not use sagas when you need strong ACID guarantees across services. In those cases, reconsider your service boundaries or accept that some operations might need to be in the same service.
Practical Implementation
Start with choreography for simple flows with 2-3 services. Switch to orchestration when the flow becomes complex or you need visibility into saga state. Use a dedicated saga orchestration framework like Axon, Temporal, or Camunda for production systems. Always implement idempotency and retry logic. Log every saga step for debugging and monitoring.
Summary
The saga pattern is the de facto standard for distributed transactions in microservices. Choose choreography for simple flows and orchestration for complex ones. Design compensating transactions carefully, handle both business and technical failures, and accept that sagas provide eventual consistency, not ACID guarantees.