Modern microservices are small, autonomous, and independently deployable — but this also creates a huge problem.
Typical pharmacy prescription management - payments, banking, logistics, supply-chain, or booking systems perform operations across multiple microservices
Each runs in its own database, service, and network boundary.
If one operation fails halfway, the entire business flow should not leave the system in an inconsistent state.
This is where the Saga Pattern becomes essential.
A Saga is a sequence of local transactions. \n Each local transaction updates data within one service and publishes an event to trigger the next step.
If a failure occurs:
| Problem | Explanation | |:---:|:---:| | 2PC is blocking | If the coordinator crashes, everything hangs. | | Microservices have independent DBs | No shared commit log across distributed DBs. | | Scales poorly | Locks span distributed systems. | | Cloud services do not support XA transactions | DynamoDB, S3, Mongo, Kafka, RDS, etc. |
A Saga avoids these issues with event-driven or orchestrated compensation logic.
\ 
Scenario
Imagine a Pharmacy Prescription Online platform where a customer places an order for a product.
The system involves three independent services:
Problem Without Saga
If one of these steps fails (e.g., payment fails), the previous steps might leave the system in an inconsistent state:
This is unacceptable in enterprise systems, especially when multiple microservices interact.
Solution With Saga
Using a Saga Orchestration Pattern, each service executes its local transaction, and in case of failure, compensation actions roll back previous steps
This ensures business-level consistency across services.
Loose Coupling: Each service remains independent, communicating only through APIs.
Below is a detailed walkthrough of the key script components from your Spring Boot Saga project.
4.1 Order Model (Order.java)
\
package com.example.orderservice; public class Order { private Long id; private String product; private String status; public Order() { // default constructor (required for Jackson) } public Order(Long id, String product, String status) { this.id = id; this.product = product; this.status = status; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getProduct() { return product; } public void setProduct(String product) { this.product = product; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } }
Purpose: Represents an order.
Business Logic: Stores the product name, order ID, and status (CREATED, CANCELLED, COMPLETED).
4.2 Order Controller (OrderController.java)
\
package com.example.orderservice; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/orders") public class OrderController { @PostMapping public Order create(@RequestParam("product") String product) { return new Order(1L, product, "CREATED"); } @PostMapping("/cancel") public String cancel() { return "ORDER_CANCELLED"; } @PostMapping("/complete") public String complete() { return "ORDER_COMPLETED"; } }
\
Endpoints
POST /orders → Creates an order.
POST /orders/cancel → Cancels an order (used for compensation).
POST /orders/complete → Marks order completed.
Why it works with Saga: Endpoints are simple, predictable, and return either the object or a status string for orchestration.
4.3 Inventory Controller (InventoryController.java)
\
package com.example.inventoryservice; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/inventory") public class InventoryController { @PostMapping("/reserve") public boolean reserve(@RequestParam("product") String product) { return !"FAIL".equals(product); } @PostMapping("/release") public void release(@RequestParam("product") String product) {} }
\
Purpose: Reserves product in stock, releases it if payment fails.
Logic: Simulates success/failure. In enterprise systems, this would check actual stock in a database.
\ 4.4. Payment Controller (PaymentController.java)
\
package com.example.paymentservice; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/payments") public class PaymentController { @PostMapping("/pay") public boolean pay(@RequestParam("amount") double amount) { return amount <= 5000; } @PostMapping("/refund") public void refund(@RequestParam double amount) {} }
\
Purpose: Processes payment and simulates failure for testing.
Why it works in Saga: Returns a Boolean indicating success/failure, allowing the orchestrator to trigger compensations.
4.5. Saga Orchestrator (SagaController.java)
\
package com.example.sagaorchestrator; import org.springframework.web.bind.annotation.*; import org.springframework.web.client.RestTemplate; @RestController @RequestMapping("/saga") public class SagaController { private final RestTemplate rest = new RestTemplate(); @PostMapping("/order") public String place(@RequestParam("product") String product, @RequestParam("amount") double amount) { Object order = rest.postForObject( "http://localhost:8081/orders?product=" + product, null, Object.class ); if (order == null) { return "Order creation failed"; } Boolean inventory = rest.postForObject( "http://localhost:8082/inventory/reserve?product=" + product, null, Boolean.class ); if (inventory == null || !inventory) { return "Inventory failed"; } Boolean payment = rest.postForObject( "http://localhost:8083/payments/pay?amount=" + amount, null, Boolean.class ); if (payment == null || !payment) { rest.postForObject( "http://localhost:8082/inventory/release?product=" + product, null, Void.class ); return "Payment failed, compensated"; } return "Order completed successfully"; } }
\
Enterprise Ready: Demonstrates clear orchestration, compensation, and business-level consistency.
\ 4.6 Verification of Outputs
\
Success Case
\
\ 
\
\ ** 
\


