Building Resilient Microservices: Orchestrator Saga Pattern with Spring Boot


The Saga design pattern is crucial in managing distributed transactions in microservices architecture. In this blog, we'll dive deep into the Saga pattern, focusing on the orchestrator-based implementation.

What is the Saga Design Pattern?

A Saga is a sequence of local transactions where each transaction updates data within a single service. If a transaction fails, the Saga executes a series of compensating transactions to undo the changes made by the preceding transactions.

Saga Pattern Variants

  1. Choreography-Based Saga: Each service produces and listens to events and decides when to execute a transaction and when to execute compensating transactions.
  2. Orchestrator-Based Saga: A centralized orchestrator manages the Saga's control flow, invoking the services' transactions and compensations.

Orchestrator-Based Saga Pattern

In this variant, an orchestrator service is responsible for managing the sequence of steps in the Saga. It tells the participants what local transactions to execute and coordinates the compensating transactions if any step fails.

Key Components

  1. Orchestrator: Centralized service managing the
Explain with springboot java code example

Implementing the Orchestrator-Based Saga Pattern in Spring Boot

In this section, we'll demonstrate how to implement the orchestrator-based Saga pattern in a Spring Boot application with a practical example.

Scenario

Consider an e-commerce application where a Saga is used to handle an order process involving multiple services: Order Service, Payment Service, and Inventory Service.

Project Structure

css
saga-orchestrator-example ├── src │ ├── main │ │ ├── java │ │ │ └── com │ │ │ └── example │ │ │ └── sagaorchestrator │ │ │ ├── controller │ │ │ ├── dto │ │ │ ├── service │ │ │ └── SagaOrchestratorApplication.java │ │ └── resources │ │ └── application.properties └── pom.xml

Step 1: Set Up Spring Boot Application

pom.xml

Add the necessary dependencies for Spring Boot and any other libraries you might need, like Feign for inter-service communication.

xml
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <!-- Add other dependencies as needed --> </dependencies>

Step 2: Create DTOs

OrderRequest.java

java
package com.example.sagaorchestrator.dto; public class OrderRequest { private Long productId; private Integer quantity; private Double price; // Getters and Setters }

OrderResponse.java

java
package com.example.sagaorchestrator.dto; public class OrderResponse { private String status; private String message; // Getters and Setters }

Step 3: Create Feign Clients for Inter-Service Communication

InventoryServiceClient.java

java
package com.example.sagaorchestrator.service; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @FeignClient(name = "inventory-service") public interface InventoryServiceClient { @PostMapping("/inventory/reduce") void reduceInventory(@RequestBody InventoryRequest inventoryRequest); }

PaymentServiceClient.java

java
package com.example.sagaorchestrator.service; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @FeignClient(name = "payment-service") public interface PaymentServiceClient { @PostMapping("/payment/process") void processPayment(@RequestBody PaymentRequest paymentRequest); }

Step 4: Create the Orchestrator Service

OrderService.java

java
package com.example.sagaorchestrator.service; import com.example.sagaorchestrator.dto.OrderRequest; import com.example.sagaorchestrator.dto.OrderResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class OrderService { @Autowired private InventoryServiceClient inventoryServiceClient; @Autowired private PaymentServiceClient paymentServiceClient; @Transactional public OrderResponse createOrder(OrderRequest orderRequest) { OrderResponse response = new OrderResponse(); try { // Step 1: Reduce Inventory inventoryServiceClient.reduceInventory(new InventoryRequest(orderRequest.getProductId(), orderRequest.getQuantity())); // Step 2: Process Payment paymentServiceClient.processPayment(new PaymentRequest(orderRequest.getPrice())); // Step 3: Complete Order response.setStatus("SUCCESS"); response.setMessage("Order placed successfully"); } catch (Exception e) { // Compensate steps if any failure occurs compensate(orderRequest); response.setStatus("FAILED"); response.setMessage("Order placement failed"); } return response; } private void compensate(OrderRequest orderRequest) { // Logic to handle compensating transactions (e.g., refund payment, restock inventory) } }

Step 5: Create Controller

OrderController.java

java
package com.example.sagaorchestrator.controller; import com.example.sagaorchestrator.dto.OrderRequest; import com.example.sagaorchestrator.dto.OrderResponse; import com.example.sagaorchestrator.service.OrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/orders") public class OrderController { @Autowired private OrderService orderService; @PostMapping public OrderResponse placeOrder(@RequestBody OrderRequest orderRequest) { return orderService.createOrder(orderRequest); } }

Step 6: Application Entry Point

SagaOrchestratorApplication.java

java
package com.example.sagaorchestrator; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableFeignClients public class SagaOrchestratorApplication { public static void main(String[] args) { SpringApplication.run(SagaOrchestratorApplication.class, args); } }

Conclusion

In this example, we demonstrated how to implement the orchestrator-based Saga pattern in a Spring Boot application. The orchestrator service coordinates the various steps of the order process, invoking services to reduce inventory and process payments, while handling compensations if any step fails. This approach ensures consistency and reliability in distributed transactions across microservices.

Post a Comment

0 Comments