How to manage distributed transactions in micro-service architecture.

How to manage distributed transactions in micro-service architecture.

Lets first understand the problem statement:

Overview

Microservice architecture breaks down a monolithic application into smaller, independent services, each with its own database and business logic. This approach enhances scalability, maintainability, and development speed. However, it also introduces significant challenges, particularly in managing transactions that span multiple services, known as distributed transactions.

The Challenge

Managing distributed transactions in a microservice architecture is inherently complex due to the need to maintain data consistency across multiple independent services. Unlike monolithic architectures, where a single transaction can easily ensure ACID (Atomicity, Consistency, Isolation, Durability) properties, microservices must coordinate between distributed databases and services, often leading to issues like partial updates, data inconsistencies, and increased latency.

Example Scenario

Consider an e-commerce platform using a microservice architecture with the following services:

  1. Order Service: Manages customer orders.
  2. Inventory Service: Tracks product stock levels.
  3. Payment Service: Handles payment processing.
  4. Shipping Service: Manages the shipment of orders.

User Story: A customer places an order. The system must:

  1. Create a new order in the Order Service.
  2. Deduct the ordered items from the Inventory Service.
  3. Process the payment through the Payment Service.
  4. Schedule the shipment through the Shipping Service.

Steps Involved

  1. Order Creation: The Order Service creates a new order and sends a request to the Inventory Service to update the stock levels.
  2. Stock Deduction: The Inventory Service deducts the stock and confirms the update.
  3. Payment Processing: The Order Service sends a request to the Payment Service to process the payment.
  4. Shipping Scheduling: Upon successful payment, the Order Service sends a request to the Shipping Service to schedule the shipment.

Potential Issues

  1. Partial Completion: If the payment processing fails after the order is created and stock is deducted, or if the shipment scheduling fails after the payment is processed, the system ends up with inconsistencies: an order exists without payment, reduced stock without a corresponding order, or an unscheduled shipment for a paid order.
  2. Concurrency: Multiple instances of services might process overlapping transactions concurrently, leading to race conditions and further inconsistencies.
  3. Network Failures: Network issues between services can lead to incomplete transactions, where some services successfully commit changes while others do not.
  4. Compensation Logic: Implementing compensation mechanisms (undoing previous steps when a subsequent step fails) is complex and often error-prone.

Consequences

  • Data Inconsistency: Inconsistent states across services can lead to incorrect business decisions, such as overselling products, failing to fulfill orders, or mismanaging shipments.
  • Complexity: Handling distributed transactions increases the complexity of the system, requiring sophisticated patterns like the Saga pattern or Two-Phase Commit (2PC), each with its own trade-offs.
  • Performance: Ensuring consistency can introduce significant latency, as services must often wait for acknowledgments from other services before proceeding.

Example of Potential Issues

  1. Concurrency: If two customers simultaneously place orders for the last available item, the Inventory Service might process stock deductions concurrently, leading to overselling and unfulfilled orders.
  2. Network Failures: A network issue between the Order Service and the Payment Service could result in an order being created and stock being deducted without the payment being processed.
  3. Partial Completion:

  • Order Creation and Stock Deduction: If the Inventory Service successfully deducts stock but the Payment Service fails to process the payment, there is an order without payment, and stock levels are incorrectly reduced.
  • Payment Processing and Shipping Scheduling: If the Payment Service successfully processes the payment but the Shipping Service fails to schedule the shipment, there is a payment without a scheduled shipment.


Compensation Logic:

If payment processing fails, the system must reverse the stock deduction and delete the order, which can be complex to implement correctly.

If shipping scheduling fails, the system must reverse the payment and restock the items, ensuring all services are correctly updated.

Till this point I hope you are got the idea about the problem statement, now lets introduce SAGA to solve this problem

The Saga design pattern is a microservices architectural pattern to manage data consistency across distributed services. It's particularly useful in complex transactions that span multiple services, such as in payment processing systems. Here’s a breakdown of the Saga design pattern explained in the context of a payment system.

Scenario: Payment Processing System

Consider a simplified payment processing system with the following services:

Order Service: Manages customer orders.

Payment Service: Processes payments.

Inventory Service: Manages product inventory.

Shipping Service: Handles the shipping of orders.

Key Concepts of the Saga Pattern

  1. Saga: A sequence of transactions (steps) that are executed to achieve a particular business goal. Each step is a transaction that updates the system state.
  2. Compensation: If any step in the sequence fails, the preceding steps are undone using compensating transactions.

Types of Sagas

  1. Choreography: Each service involved in the saga publishes events and listens for events from other services. This approach is decentralized and doesn't require a central coordinator.
  2. Orchestration: A central coordinator (orchestrator) tells each service what local transaction to execute next. This approach is centralized.

Example: Order Processing Saga

Let's implement a saga using the orchestration approach for an order processing system. Steps in the Saga

  1. Create Order: The Order Service creates an order in a pending state.
  2. Reserve Inventory: The Inventory Service reserves the necessary items.
  3. Process Payment: The Payment Service processes the payment.
  4. Ship Order: The Shipping Service ships the order.
  5. Complete Order: The Order Service marks the order as completed.


Compensating Transactions

  1. If reserving inventory fails, the order is canceled.
  2. If payment processing fails, the reserved inventory is released.
  3. If shipping fails, the payment is refunded and the reserved inventory is released.

Implementation Steps:

Orchestrator: A central service that manages the saga.

Services: Each service (Order, Inventory, Payment, Shipping) performs its part of the transaction and reports back to the orchestrator. Step-by-Step Example Order Service - Create Order

@RestController
public class OrderService {

    @Autowired
    private SagaOrchestrator sagaOrchestrator;

    @PostMapping("/orders")
    public ResponseEntity<Order> createOrder(@RequestBody OrderRequest orderRequest) {
        Order order = new Order();
        order.setStatus("PENDING");
        // Save order to database
        // ...

        // Start the saga
        sagaOrchestrator.createOrderSaga(order);
        return ResponseEntity.ok(order);
    }
}
        

Saga Orchestrator

@Service
public class SagaOrchestrator {

    @Autowired
    private InventoryService inventoryService;

    @Autowired
    private PaymentService paymentService;

    @Autowired
    private ShippingService shippingService;

    public void createOrderSaga(Order order) {
        try {
            inventoryService.reserveInventory(order);
            paymentService.processPayment(order);
            shippingService.shipOrder(order);
            completeOrder(order);
        } catch (Exception e) {
            compensate(order);
        }
    }

    private void completeOrder(Order order) {
        order.setStatus("COMPLETED");
        // Update order status in database
        // ...
    }

    private void compensate(Order order) {
        // Rollback steps in reverse order
        shippingService.cancelShipment(order);
        paymentService.refundPayment(order);
        inventoryService.releaseInventory(order);
        cancelOrder(order);
    }

    private void cancelOrder(Order order) {
        order.setStatus("CANCELED");
        // Update order status in database
        // ...
    }
}
        

Inventory Service

@Service
public class InventoryService {

    public void reserveInventory(Order order) throws Exception {
        // Reserve items in inventory
        // ...
        if (/* failure condition */) {
            throw new Exception("Inventory reservation failed");
        }
    }

    public void releaseInventory(Order order) {
        // Release reserved items
        // ...
    }
}
        

Payment Service

@Service
public class PaymentService {

    public void processPayment(Order order) throws Exception {
        // Process payment
        // ...
        if (/* failure condition */) {
            throw new Exception("Payment processing failed");
        }
    }

    public void refundPayment(Order order) {
        // Refund payment
        // ...
    }
}
        

Shipping Service

@Service
public class ShippingService {

    public void shipOrder(Order order) throws Exception {
        // Ship order
        // ...
        if (/* failure condition */) {
            throw new Exception("Shipping failed");
        }
    }

    public void cancelShipment(Order order) {
        // Cancel shipment
        // ...
    }
}
        

Benefits of the Saga Pattern Resilience: Each step of the saga can be retried or compensated, which makes the system more resilient to failures.

Scalability: Each service can scale independently.

Consistency: Ensures eventual consistency across distributed services.

Conclusion: The Saga pattern is essential for maintaining data consistency in a microservices architecture, especially for complex, long-running transactions like order processing in a payment system. By implementing sagas with either orchestration or choreography, you can manage distributed transactions effectively, ensuring resilience, scalability, and consistency.


Vikram Saini

System Analyst | Solution Architect | Integration Expert | Java Developer

10mo

Very informative

To view or add a comment, sign in

More articles by Anuj Kumar

Insights from the community

Others also viewed

Explore topics