CodeGym /Courses /Module 5. Spring /Lecture 212: Introduction to the Saga pattern (Saga)

Lecture 212: Introduction to the Saga pattern (Saga)

Module 5. Spring
Level 14 , Lesson 1
Available

Saga in the world of distributed systems is a pattern that helps manage long-running transactions (the kind that span multiple microservices in a single process). Put simply, it's a chain of sequential steps that execute local transactions across different services, ensuring atomicity through compensating actions.

For a monolith, ACID (Atomicity, Consistency, Isolation, Durability) transactions are normal. But in a microservices architecture they're, unfortunately, like square wheels on a bike: it kind of moves, but not the way you'd like. That's where Saga comes in: it supports eventual consistency, which is more realistic in the microservices world.


History and why it came to be

The Saga pattern first appeared in 1987 thanks to the work of Hector Garcia-Molina and Kenneth B. Kim. Its main idea was to split a long-running transaction into a series of manageable operations with possible compensation. Sound complicated? Imagine a restaurant that takes your order (a local transaction), but if the ingredients run out, they offer to cancel your order or pick another dish (a compensating action).

When microservices became mainstream, Saga came back as a way to coordinate distributed transactions. And frameworks like Spring Boot, by the way, provide plenty of tools to implement it.


How does a Saga work?

A Saga is divided into several steps. They run sequentially or in parallel, and each step is wrapped in a local transaction. The idea is simple:

  1. The first step starts (for example, reserving a product).
  2. If it succeeds, we move to the second step (say, charging the payment).
  3. If some step fails (oops, the card has no funds), compensating transactions kick in (cancel the product reservation).

The Saga pattern focuses on two things: primary actions (e.g., buying a ticket) and compensating actions (refunds if the ticket wasn't purchased).

Usage examples

Example 1. Booking a trip

  • You book a flight, a hotel, and a rental car.
  • If the hotel booking fails, you need to cancel the flight and the car reservation.

Example 2. Buying a product in an online store

  • Decrease inventory stock.
  • Charge the customer's card.
  • Send a notification email.
  • If any step fails, you need to refund the money and cancel the order.

Benefits of Saga

Saga helps balance the flexibility of a distributed system with the need to maintain data consistency. Here are a few reasons why it's important:

  1. Data consistency: Saga solves the problem of keeping synchronized data despite failures.
  2. Flexibility: Saga works for both synchronous and asynchronous calls.
  3. Scalability: each service has its own local state, independent of a global transaction manager.
  4. Ease of recovery: if something goes wrong, you just run compensating actions.

Another example

Imagine your online store is built on microservices using Saga:

  • Product service: reserves the product.
  • Payment service: charges the money.
  • Shipping service: creates an order for the courier.

The buyer adds a product to the cart, pays, waits for delivery, everything goes smoothly… until the courier says: "Sorry, the delivery address doesn't exist." Saga saves the day: the shipping service tells the product service to release the reservation, and the payment service to refund the money. The customer is happy again.


Limitations?

Saga is awesome, but it's not perfect. Here's what to keep in mind:

  • No immediate consistency: Saga provides eventual consistency, which might be unacceptable for critically important data.
  • Implementation complexity: the more steps in a business process, the harder it is to manage the saga.
  • Compensating actions: they are often hard to design and test (fun fact: canceling a "pizza delivery" is easier than canceling a "rocket launch").

Example of using the Saga pattern in code

For illustration, let's implement a basic scenario: reserving a product in an online store, charging payment, and creating a shipment. Our process will look like this:

  1. Reserve the product.
  2. Charge the order.
  3. Create the shipment.

Step 1: Define events


public class ReserveProductCommand {
    private String productId;
    private int quantity;
    private String orderId;
    // Constructors, getters and setters
}

public class PaymentCommand {
    private String orderId;
    private double amount;
    // Constructors, getters and setters
}

public class ShipOrderCommand {
    private String orderId;
    private String address;
    // Constructors, getters and setters
}

Step 2: Reserve product


@Service
public class InventoryService {

    public void reserveProduct(ReserveProductCommand command) {
        // Logic for reserving the product
        System.out.println("Reserving product " + command.getProductId());
    }

    public void cancelReservation(String productId) {
        // Compensating transaction
        System.out.println("Canceling reservation of product " + productId);
    }
}

Step 3: Process payment


@Service
public class PaymentService {

    public void processPayment(PaymentCommand command) {
        // Logic for charging payment
        System.out.println("Charging " + command.getAmount() + " for order " + command.getOrderId());
    }

    public void refundPayment(String orderId) {
        // Compensating transaction
        System.out.println("Refunding payment for order " + orderId);
    }
}

Step 4: Organizing the Saga


@Service
public class OrderSagaService {

    @Autowired
    private InventoryService inventoryService;

    @Autowired
    private PaymentService paymentService;

    public void processOrder(String orderId, String productId, int quantity, double amount) {
        try {
            // Step 1: Reserve the product
            inventoryService.reserveProduct(new ReserveProductCommand(productId, quantity, orderId));

            // Step 2: Charge the payment
            paymentService.processPayment(new PaymentCommand(orderId, amount));

            // Step 3: Create shipment
            System.out.println("Creating shipment for order: " + orderId);

        } catch (Exception ex) {
            // Error handling and triggering compensations
            inventoryService.cancelReservation(productId);
            paymentService.refundPayment(orderId);
        }
    }
}

This simple example illustrates the basic process of implementing a Saga: calling services, handling errors, and executing compensating actions.

Bonus: the implementation can be greatly simplified and systematized using Spring State Machine, Axon Framework or other tools.


With this foundation you're ready to move on — to discussing Saga orchestration and choreography, which let you organize complex business processes even more simply and elegantly.

Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION