The Saga design pattern

The Saga design pattern is a pattern used in distributed systems to manage long-running transactions that involve multiple services. It helps to ensure that these transactions are executed in a consistent manner, even if there are failures or other issues during the process. The Saga pattern consists of a series of steps or actions, each of which is represented by a separate transaction that can be rolled back if necessary.



Here are the key steps involved in implementing the Saga pattern:
Define the steps: Identify the sequence of steps required to complete the transaction. Each step should be designed as an independent transaction that can be either committed or rolled back.

Define the compensating actions: For each step in the transaction, define a compensating action that can be executed if the step fails or needs to be rolled back.

Implement the transaction manager: The transaction manager is responsible for coordinating the transaction across multiple services. It should be designed to keep track of the current state of the transaction and be able to roll back any completed steps if necessary.

Implement the saga coordinator: The saga coordinator is responsible for executing the individual steps in the transaction and managing any compensating actions that need to be taken.

Execute the transaction: Once the transaction is initiated, the saga coordinator executes each step in the transaction. If a step fails, the compensating action for that step is executed to undo the effects of the failed step. If all steps complete successfully, the transaction is committed.

There are two ways of coordination sagas:
Choreography - each local transaction publishes domain events that trigger local transactions in other services
Orchestration - an orchestrator (object) tells the participants what local transactions to execute

1. Choreography-Based Saga Design Pattern
2. Orchestration-based saga




In Choreography-Based Saga design pattern, there is no central coordinator responsible for managing the Saga. Instead, each service involved in the transaction communicates with other services directly to determine the appropriate actions to take. This means that each service has to understand the context of the transaction and know what actions to take based on its own state and the messages it receives from other services.

Here is an example to better understand the Choreography-Based Saga design pattern:
Suppose you have an e-commerce application that allows customers to place orders for products. When a customer places an order, the application needs to perform several actions, such as verifying the payment information, checking the inventory levels, and shipping the products.

In Choreography-Based Saga, each service involved in the transaction would communicate with the other services to determine the appropriate actions to take. For example, the payment service would communicate with the inventory service to check if the products are available and reserve them if necessary. Similarly, the shipping service would communicate with the payment service to verify that the payment has been processed before shipping the products.

The advantage of using Choreography-Based Saga is that it can lead to a more loosely coupled architecture, which can be more flexible and easier to scale. However, it also requires each service to understand the context of the transaction and know what actions to take, which can be more complex.

Overall, Choreography-Based Saga is a powerful tool for managing complex, distributed transactions in a more flexible and loosely coupled architecture. However, it requires careful coordination among services to ensure that the transaction is executed in a consistent and reliable manner.


Here's a code example for a Choreography-Based Saga in Java:
Suppose we have three services involved in a transaction: payment, inventory, and shipping. When a customer places an order, the payment service needs to verify the payment information, the inventory service needs to check the inventory levels, and the shipping service needs to ship the products.

1. Payment Service

public class PaymentService {
public void processPayment(String orderId, double amount) {
// Verify payment information and charge the customer
if (verifyPayment(orderId, amount)) {
// Notify other services of payment success
InventoryService.notifyPaymentSuccess(orderId);
ShippingService.notifyPaymentSuccess(orderId);
} else {
// Notify other services of payment failure
InventoryService.notifyPaymentFailure(orderId);
ShippingService.notifyPaymentFailure(orderId);
}
}

private boolean verifyPayment(String orderId, double amount) {
// Verify payment information and charge the customer
// ...
}
}


2. Inventory Service

public class InventoryService {
public void reserveInventory(String orderId) {
// Check inventory levels and reserve the products
if (checkInventory(orderId)) {
// Notify other services of inventory reservation success
PaymentService.notifyInventoryReservationSuccess(orderId);
ShippingService.notifyInventoryReservationSuccess(orderId);
} else {
// Notify other services of inventory reservation failure
PaymentService.notifyInventoryReservationFailure(orderId);
ShippingService.notifyInventoryReservationFailure(orderId);
}
}
private boolean checkInventory(String orderId) {
// Check inventory levels and reserve the products
// ...
}
}

3. Shipping Service

public class ShippingService {
public void shipProducts(String orderId) {
// Verify payment and inventory reservation before shipping the products
if (verifyPaymentAndInventoryReservation(orderId)) {
// Ship the products
// ...
} else {
// Notify other services of shipping failure
PaymentService.notifyShippingFailure(orderId);
InventoryService.notifyShippingFailure(orderId);
}
}
private boolean verifyPaymentAndInventoryReservation(String orderId) {
// Verify payment and inventory reservation before shipping the products
// ...
}
}

In this example, each service communicates directly with the other services to determine the appropriate actions to take. For example, the PaymentService notifies the InventoryService and ShippingService of payment success or failure, and the InventoryService notifies the PaymentService and ShippingService of inventory reservation success or failure.
Orchestration-based saga design



Orchestration-Based Saga is a design pattern for managing distributed transactions in microservices architecture. In this pattern, there is a centralized coordinator service that orchestrates the transaction and manages the state of the transaction. The coordinator service communicates with the other services to determine the appropriate actions to take in each step of the transaction.

Let's consider the same example of a customer placing an order from the previous question. Here's how an Orchestration-Based Saga can be implemented:

Coordinator Service
public class OrderCoordinator {
private PaymentService paymentService;
private InventoryService inventoryService;
private ShippingService shippingService;
public void placeOrder(String orderId, double amount) {
// Initialize the transaction
Transaction transaction = new Transaction(orderId, amount);
// Perform the payment step
paymentService.processPayment(transaction);
// Perform the inventory reservation step
inventoryService.reserveInventory(transaction);
// Perform the shipping step
shippingService.shipProducts(transaction);
// Finalize the transaction
finalizeTransaction(transaction);
}
private void finalizeTransaction(Transaction transaction) {
// Update the transaction state
// ...
}
}


Payment Service

public class PaymentService {
public void processPayment(Transaction transaction) {
// Verify payment information and charge the customer
if (verifyPayment(transaction)) {
// Notify the coordinator of payment success
coordinator.notifyPaymentSuccess(transaction);
} else {
// Notify the coordinator of payment failure
coordinator.notifyPaymentFailure(transaction);
}
}
private boolean verifyPayment(Transaction transaction) {
// Verify payment information and charge the customer
// ...
}
}

Inventory Service

public class InventoryService {
public void reserveInventory(Transaction transaction) {
// Check inventory levels and reserve the products
if (checkInventory(transaction)) {
// Notify the coordinator of inventory reservation success
coordinator.notifyInventoryReservationSuccess(transaction);
} else {
// Notify the coordinator of inventory reservation failure
coordinator.notifyInventoryReservationFailure(transaction);
}
}
private boolean checkInventory(Transaction transaction) {
// Check inventory levels and reserve the products
// ...
}
}

Shipping Service

public class ShippingService {
public void shipProducts(Transaction transaction) {
// Verify payment and inventory reservation before shipping the products
if (verifyPaymentAndInventoryReservation(transaction)) {
// Ship the products
// ...
// Notify the coordinator of shipping success
coordinator.notifyShippingSuccess(transaction);
} else {
// Notify the coordinator of shipping failure
coordinator.notifyShippingFailure(transaction);
}
}
private boolean verifyPaymentAndInventoryReservation(Transaction transaction) {
// Verify payment and inventory reservation before shipping the products
// ...
}
}

In this example, the Coordinator Service is the centralized service that manages the transaction and communicates with the other services to determine the appropriate actions to take. The Coordinator Service initiates the transaction by calling the appropriate methods on the Payment Service, Inventory Service, and Shipping Service. Each service performs its own business logic and notifies the Coordinator Service of the success or failure of its step. The Coordinator Service then finalizes the transaction based on the results of the steps.


Choise between Orchestration-based saga VS Choreography-based saga

The choice between Choreography-based and Orchestration-based Saga design patterns depends on the specific requirements and constraints of your system. Both patterns have their own pros and cons.

Choreography-based Saga can be a good choice when you have a large number of services in your system, each with their own business logic, and you want to minimize the dependencies between services. In this pattern, each service communicates with its neighboring services directly, without the need for a central coordinator. However, this approach can lead to a more complex system, with potentially longer recovery times in case of failures.

On the other hand, Orchestration-based Saga is a good fit when you need a more centralized control over the transaction flow, and when the transaction spans across multiple services with a defined sequence of steps. The Coordinator service manages the overall flow and directs each participating service when to perform their specific business logic. This approach can simplify the system architecture and provide better visibility and control over the transaction flow. However, it can also introduce a single point of failure and tight coupling between services.

In summary, the choice between the two patterns depends on the specific needs and constraints of your system.


Any content issue please reachout : thirupathiraju52@gmil.com