What Is Saga Architecture Pattern?
The Saga design pattern is a useful tool for ensuring data consistency when dealing with distributed transactions across microservices. This pattern involves creating a series of transactions that update microservices sequentially and trigger events to initiate the next transaction for the next microservice.
SAGA is a design pattern that consists of a sequence of smaller transactions that are executed over a longer period. This pattern is used to ensure that data consistency is maintained across multiple microservices. Each transaction is executed by a single service, and the state changes are broadcast to other services involved in the Saga. By doing so, SAGA helps to maintain data consistency, providing an alternative approach to the traditional ACID transactions model that may not always be feasible in a distributed environment.
When dealing with complex business processes that involve multiple services, such as order processing, shipping, and billing, it’s important to maintain data consistency across all services involved. To achieve this, a system is responsible for managing the overall transaction and coordinating the compensating actions required for failures. This system is useful for coordinating the execution of multiple transactions and ensuring that data consistency is maintained across all services involved.
The illustration below depicts how an order processing system is implemented via Step by Step Functions using the saga pattern. Each step, like “ProcessPayment”, has its steps to handle the process’s success, such as “UpdateCustomerAccount”, and its failures, such as “SetOrderFailure”.

Considerations
- The Saga pattern may initially be challenging, as it requires a new way of thinking on how to coordinate a transaction and maintain data consistency for a business process spanning multiple microservices.
- Data can’t be rolled back, because saga participants commit changes to their local databases.
- To keep data consistent across multiple microservices without creating a strong dependency, the application should use a loose coupling approach.
- Long transactions shouldn’t block other microservices.
- The application needs to be designed to allow rollbacks in case of an operation failure.
Important
The Saga pattern can be complex to debug and requires a programming model that includes compensating transactions for rolling back and undoing changes. As the number of microservices increases, the pattern’s complexity also increases.
Why We Need Saga Pattern?
The Microservice architecture allows us to keep application services and their databases separate. However, due to the complexity of modern-day requirements, data changes often need to be applied to multiple services at once.
For example, let’s consider a simple online order system built with Microservices. There are separate Microservices for;
- Order processing.
- Payment processing.
- Updating Customer Account.
- Sending confirmations to customers.
Suppose a customer makes an order. It requires invoking several Microservices in sequence to complete the flow starting from order, payment, Update Account, and sending confirmation.
But, what happens if any of these steps fail? We somehow need to roll back any previous steps to maintain data integrity.
Problem without SAGA
Microservice architecture is a way of structuring an application into a set of independent microservices that are loosely coupled. Each microservice can be developed independently in an agile manner, which enables continuous delivery/deployment. However, if we have used a database per service design pattern, then implementing a transaction that spans multiple services can be challenging.
Solution
The Saga pattern provides transaction management using a sequence of local transactions. A local transaction is the atomic work effort performed by a saga participant. Each local transaction updates the database and publishes a message or event to trigger the next local transaction in the saga. If a local transaction fails, the saga executes a series of rollback transactions that undo the changes that were made by the preceding local transactions.
Approaches of Sagas
There are 2 approaches to implementing the Saga pattern, Choreography-Based Saga and Orchestration-Based Saga.
1. Orchestration-Based Saga
An Orchestration-Based Saga is a system where a single orchestrator manages all transactions and directs services to execute local transactions. The orchestrator acts as a central controller of all local transactions and keeps track of the status of the complete transaction.

Let’s consider the same example I explained earlier and break down Orchestration-Based Saga into steps.
Step 1 — The user makes the order request
When a user makes a new order request, the order service receives the POST request and creates a new Saga orchestrator for the order process.
Step 2 — Orchestrator creates a new order
Then the orchestrator creates a new order in the pending state and sends the payment process command to the payment service.
Step 3 — Execute all the services accordingly
After the payment is processed successfully, the orchestrator will call the customer account updating service. Likewise, all the services will be called one by one, and the orchestrator will be notified whether the transactions have succeeded or failed.
Step 4 — Approve or reject the order
After each service completes its local transaction, it informs the transaction status to the orchestrator. Based on that feedback, the orchestrator can approve or reject the order by updating its state.
Orchestration-based saga is simpler compared to a Choreography-Based Saga, and it is most suitable for situations like,
- When there are already implemented Microservices.
- When a large number of Microservices participate in a single transaction.
2. Choreography-Based Saga
In a Choreography-Based Saga, all the services that are part of the distributed transaction publish a new event after completing their local transaction.
The Choreography-Based Saga approach does not have an orchestrator to direct and execute local transactions. Instead, each Microservice is responsible for generating a new event. And it will trigger the transaction of the next Microservice.
SAGA execution controller keeps track of all these events using the SEC log and executes rollback events in case of a failure. For example, the order process would look like this with the Choreography-Based SAGA approach.
But, what would happen if the order updating transaction fails? Then, the order updating service will inform the SEC about the failure, and the SEC will start the corresponding rollback events and end the order process by setting the state to fail.

As you can see, this approach is more complex than the Orchestration-Based Saga approach. So the Choreography-Based Saga approach is more suitable for situations like,
- When you implement new Microservices from scratch.
- When a small number of Microservices participate in a single transaction.
Advantages and Disadvantages of SAGA Pattern
Advantages
- Provides better fault tolerance: if one step fails, the remaining steps are unaffected. This means that any failed step can be reversed or corrected without disrupting other steps in the process.
- Simplifies error handling: SAGA simplifies error handling and corrections by creating a consistent approach. This helps with debugging and maintenance.
- Allows for asynchronous processing: It allows it to handle multiple tasks simultaneously, making it faster and more efficient.
- Supports distributed transactions: SAGA is a system that can handle transactions across multiple services or databases. This means that it can support more scalable and distributed architectures
Disadvantage
- Increased complexity: To implement SAGA, you need to write additional code and design an architecture that can handle compensation and rollback steps.
- Limited support: Implementing SAGA can be challenging if all the platforms do not support it.
- Requires careful design: To implement the SAGA pattern correctly, it needs to be designed with care. This is important to ensure that it can handle all possible failure scenarios and perform compensations and rollbacks as needed.
- Increased latency: When using SAGA, it’s important to note that there may be some extra time added to processes due to coordination needed between different services or databases.
Setting up Microservice
For this example, we will use the Orchestration-Based Saga pattern to create two Spring Boot microservices: Order Service, Payment Service and SagaOrchestrator service. Each service will have its own PostgreSQL database.
1. Order Service Code:
@Service
public class OrderService {
@Autowired
private JmsTemplate jmsTemplate;
@Autowired
private OrderRepository orderRepository;
@Transactional
public void placeOrder(Order order) {
orderRepository.save(order);
jmsTemplate.convertAndSend("orderQueue", order);
}
@Transactional
public void confirmOrder(Long orderId) {
Order order = orderRepository.findById(orderId).orElse(null);
if (order != null) {
order.setConfirmed(true);
orderRepository.save(order);
}
}
@Transactional
public void cancelOrder(Long orderId) {
Order order = orderRepository.findById(orderId).orElse(null);
if (order != null) {
orderRepository.delete(order);
}
}
}
2. Payment Service Code
@Service
public class PaymentService {
@Autowired
private JmsTemplate jmsTemplate;
@Autowired
private PaymentRepository paymentRepository;
@Transactional
public void processPayment(Order order) {
Payment payment = new Payment();
payment.setOrderId(order.getId());
payment.setAmount(calculatePaymentAmount(order));
paymentRepository.save(payment);
jmsTemplate.convertAndSend("paymentQueue", payment);
}
@Transactional
public void confirmPayment(Long orderId) {
Payment payment = paymentRepository.findByOrderId(orderId);
if (payment != null) {
payment.setPaid(true);
paymentRepository.save(payment);
}
}
@Transactional
public void cancelPayment(Long orderId) {
Payment payment = paymentRepository.findByOrderId(orderId);
if (payment != null) {
paymentRepository.delete(payment);
}
}
private double calculatePaymentAmount(Order order) {
// Implement your payment calculation logic here
return order.getRoomPrice() * order.getNumNights();
}
}
3. SagaOrchestrator Service Code
@Service
public class SagaOrchestrator {
@Autowired
private OrderService orderService;
@Autowired
private PaymentService paymentService;
@JmsListener(destination = "orderQueue")
public void handleOrder(Order order) {
}
@JmsListener(destination = "paymentQueue")
public void handlePayment(Payment payment) {
try {
// step 2: confirm payment is success or failed. If it's failed
// It's failure, throw exception and rollback.
paymentService.confirmPayment(payment.getOrderId());
// Step 3: Mark status is comfirmed in order.
orderService.confirmOrder(payment.getOrderId())
} catch (Exception e) {
// Handle exceptions and initiate compensation
orderService.cancelOrder(order.getId());
paymentService.cancelPayment(payment.getOrderId());
}
}
}
Concussion
By leveraging SAGA, we can effectively implement distributed transactions using two distinct approaches, both of which have been elaborated upon with sample code. By following these methods, we can ensure that our transactions are executed seamlessly and efficiently.



