Local transactions
Each service commits its own work independently.
Sagas manage multi-step workflows across service boundaries using local transactions plus compensating actions. They are not magic replacements for ACID across the platform. They are a way to make business processes recoverable when ordering, payment, inventory, and shipment each own their own persistence and lifecycle.
Placing an order may reserve inventory, authorize payment, and create shipment planning. If payment fails after reservation succeeds, the system needs a business recovery path. A saga names that recovery path explicitly instead of relying on impossible cross-service rollback semantics.
Sagas break a large distributed transaction into local transactions linked by messages or commands. If a later step fails, compensating actions undo or mitigate earlier work where the business allows it. There are two common coordination styles. Orchestration uses a central coordinator. Choreography lets services react to events without one central controller. Both can work, but both require careful state thinking.
The main discipline is business realism. Not every action is fully reversible. Compensation may mean refunding, releasing stock, or marking a case for manual review. A saga succeeds when the business outcome becomes explicit and recoverable, not when it pretends rollback is free.
Each service commits its own work independently.
Failures trigger business-aware recovery actions, not low-level rollback fantasies.
Choose orchestration for visibility or choreography for looser coupling, based on workflow complexity.
| Style | Good fit | Watch for |
|---|---|---|
| Orchestration | Complex workflows that need visible central coordination | Over-centralized process logic becoming a hidden monolith |
| Choreography | Looser event collaboration with simpler flows | Implicit coupling and hard-to-follow event chains |
package org.javaomnibus.orders.saga;
public final class PlaceOrderSaga {
private final InventoryService inventoryService;
private final PaymentService paymentService;
public PlaceOrderSaga(InventoryService inventoryService, PaymentService paymentService) {
this.inventoryService = inventoryService;
this.paymentService = paymentService;
}
public void execute(OrderId orderId, List<OrderLine> lines, Money total) {
inventoryService.reserve(orderId, lines);
try {
paymentService.authorize(orderId, total);
} catch (RuntimeException failure) {
inventoryService.release(orderId);
throw failure;
}
}
}