How to implement saga orchestration with compensating steps in Python
Orchestrate a distributed transaction across services, rolling back completed steps with compensations when a later step fails.
Python code
62 linesclass InventoryService:
def reserve(self, order_id):
print(f"[Inventory] Reserving stock for order {order_id}")
return True
def compensate(self, order_id):
print(f"[Inventory] Releasing stock for order {order_id}")
class PaymentService:
def charge(self, order_id):
print(f"[Payment] Charging customer for order {order_id}")
return True
def compensate(self, order_id):
print(f"[Payment] Refunding customer for order {order_id}")
class ShippingService:
def ship(self, order_id):
print(f"[Shipping] Shipping order {order_id}")
return True
def compensate(self, order_id):
print(f"[Shipping] Canceling shipment for order {order_id}")
def place_order(order_id):
inventory = InventoryService()
payment = PaymentService()
shipping = ShippingService()
steps = [
("reserve inventory", lambda: inventory.reserve(order_id), lambda: inventory.compensate(order_id)),
("charge payment", lambda: payment.charge(order_id), lambda: payment.compensate(order_id)),
("ship order", lambda: shipping.ship(order_id), lambda: shipping.compensate(order_id)),
]
executed = []
for step_name, action, compensate in steps:
print(f"Executing: {step_name}")
if not action():
print(f"FAILED at: {step_name}")
print("Starting compensation...")
for executed_name, executed_compensate in reversed(executed):
print(f"Compensating: {executed_name}")
executed_compensate()
return False
executed.append((step_name, compensate))
print("Order placed successfully!")
return True
if __name__ == "__main__":
# Simulate a successful flow
place_order("ORD-123")
print("-" * 40)
# Simulate a failure during payment
PaymentService.charge = lambda self, order_id: False # monkey-patch to fail
place_order("ORD-456")
Output
Executing: reserve inventory
[Inventory] Reserving stock for order ORD-123
Executing: charge payment
[Payment] Charging customer for order ORD-123
Executing: ship order
[Shipping] Shipping order ORD-123
Order placed successfully!
----------------------------------------
Executing: reserve inventory
[Inventory] Reserving stock for order ORD-456
Executing: charge payment
FAILED at: charge payment
Starting compensation...
Compensating: reserve inventory
[Inventory] Releasing stock for order ORD-456
How it works
The saga pattern breaks a distributed transaction into discrete steps, each with a paired compensation action. The steps list stores (name, action, compensate) tuples, and the executed list tracks only completed steps so we know what to roll back. When a step returns False, the loop exits and replays compensations in reverse order (LIFO) to undo prior work. This mirrors real-world systems where services like inventory, payment, and shipping must stay consistent without a single database transaction. The mock services print their actions so you can see exactly when reservations, charges, and compensations happen.
Common mistakes
- Compensating steps in forward order instead of reversed, which breaks dependencies
- Forgetting to append the compensation function to `executed` before running the action
- Not handling exceptions thrown inside `action()`, only checking boolean returns
- Assuming compensation is always safe to run even if a step never fully completed
Variations
- Use a saga coordinator class to encapsulate step registration and rollback logic
- Async version with asyncio where each step awaits external service calls and retries on transient failures
Real-world use cases
- Coordinating order fulfillment across inventory, payment, and shipping microservices when a payment fails and inventory must be released.
- Rolling back a multi-step onboarding flow (create account, provision resources, send welcome email) when provisioning errors out.
- Managing refund workflows in travel booking systems where hotel, flight, and car rental reservations each need cleanup.
Sponsored
More from System design patterns
- Build a BFF (Backend for Frontend) Mock Aggregator in Python medium
- Builder pattern for mocking complex objects in Python easy
- Circuit Breaker Pattern in Python: Closed, Open, and Half-Open States medium
- Create a Data Helper Class in Python easy
- Domain Driven Design Aggregate Root Example in Python medium
- Facade Pattern in Python with Mock Simplification medium
Keep learning
Related tutorials and quizzes for this topic.