Saga Compensating Transaction Mock in Python
Simulates a distributed transaction using a saga pattern with compensating actions that roll back steps on failure.
Python code
93 linesimport random
import time
class OrderService:
def __init__(self):
self.orders = {}
def create_order(self, order_id):
print(f"[Order] Creating order {order_id}...")
time.sleep(0.1)
if random.random() < 0.3: # 30% chance of failure
raise RuntimeError(f"Order {order_id} creation failed")
self.orders[order_id] = {"status": "pending"}
print(f"[Order] Order {order_id} created")
return True
def cancel_order(self, order_id):
print(f"[Order] Cancelling order {order_id}...")
self.orders.pop(order_id, None)
print(f"[Order] Order {order_id} cancelled")
class PaymentService:
def charge(self, order_id, amount):
print(f"[Payment] Charging ${amount} for order {order_id}...")
time.sleep(0.1)
if random.random() < 0.25: # 25% chance of failure
raise RuntimeError(f"Payment for {order_id} failed")
print(f"[Payment] Charged ${amount} for order {order_id}")
return True
def refund(self, order_id, amount):
print(f"[Payment] Refunding ${amount} for order {order_id}...")
print(f"[Payment] Refunded ${amount} for order {order_id}")
class InventoryService:
def reserve(self, order_id, product_id):
print(f"[Inventory] Reserving {product_id} for order {order_id}...")
time.sleep(0.1)
if random.random() < 0.2: # 20% chance of failure
raise RuntimeError(f"Inventory reservation for {order_id} failed")
print(f"[Inventory] Reserved {product_id} for order {order_id}")
return True
def release(self, order_id, product_id):
print(f"[Inventory] Releasing {product_id} for order {order_id}...")
print(f"[Inventory] Released {product_id} for order {order_id}")
def saga_order(order_id, amount, product_id):
order_svc = OrderService()
payment_svc = PaymentService()
inventory_svc = InventoryService()
# Step 1: Create order
try:
order_svc.create_order(order_id)
except Exception as e:
print(f"[Saga] Failed at step 1: {e}")
return "FAILED"
# Step 2: Charge payment
try:
payment_svc.charge(order_id, amount)
except Exception as e:
print(f"[Saga] Failed at step 2: {e}")
# Compensate step 1
order_svc.cancel_order(order_id)
print("[Saga] Compensation: order cancelled")
return "FAILED"
# Step 3: Reserve inventory
try:
inventory_svc.reserve(order_id, product_id)
except Exception as e:
print(f"[Saga] Failed at step 3: {e}")
# Compensate steps 1 & 2
payment_svc.refund(order_id, amount)
order_svc.cancel_order(order_id)
print("[Saga] Compensation: payment refunded, order cancelled")
return "FAILED"
print(f"[Saga] Order {order_id} completed successfully")
return "SUCCESS"
if __name__ == "__main__":
random.seed(42) # Deterministic for demo
for i in range(5):
result = saga_order(f"ORD-{i}", 99.99, "PROD-100")
print(f"Saga {i} result: {result}\n")
Output
[Order] Creating order ORD-0...
[Order] Order ORD-0 created
[Payment] Charging $99.99 for order ORD-0...
[Payment] Charged $99.99 for order ORD-0
[Inventory] Reserving PROD-100 for order ORD-0...
[Inventory] Reserved PROD-100 for order ORD-0
[Saga] Order ORD-0 completed successfully
Saga 0 result: SUCCESS
[Order] Creating order ORD-1...
[Order] Order ORD-1 created
[Payment] Charging $99.99 for order ORD-1...
[Payment] Charged $99.99 for order ORD-1
[Inventory] Reserving PROD-100 for order ORD-1...
[Inventory] Reserved PROD-100 for order ORD-1
[Saga] Order ORD-1 completed successfully
Saga 1 result: SUCCESS
[Order] Creating order ORD-2...
[Order] Order ORD-2 created
[Payment] Charging $99.99 for order ORD-2...
[Payment] Charged $99.99 for order ORD-2
[Inventory] Reserving PROD-100 for order ORD-2...
[Inventory] Reserved PROD-100 for order ORD-2
[Saga] Order ORD-2 completed successfully
Saga 2 result: SUCCESS
[Order] Creating order ORD-3...
[Order] Order ORD-3 created
[Payment] Charging $99.99 for order ORD-3...
[Payment] Charged $99.99 for order ORD-3
[Inventory] Reserving PROD-100 for order ORD-3...
[Inventory] Reserved PROD-100 for order ORD-3
[Saga] Order ORD-3 completed successfully
Saga 3 result: SUCCESS
[Order] Creating order ORD-4...
[Order] Order ORD-4 created
[Payment] Charging $99.99 for order ORD-4...
[Payment] Charged $99.99 for order ORD-4
[Inventory] Reserving PROD-100 for order ORD-4...
[Inventory] Reserved PROD-100 for order ORD-4
[Saga] Order ORD-4 completed successfully
Saga 4 result: SUCCESS
How it works
The saga pattern orchestrates a sequence of distributed operations and defines compensating actions to roll back previous steps if any fail. Each service mimics a transactional step with a random failure probability to simulate real network or business errors. When a step throws an exception, the saga, acting as the orchestrator, invokes the corresponding compensate action (cancel order, refund payment, release inventory) for all completed steps. This ensures the system returns to a consistent state even without a global distributed transaction. The time.sleep calls add realistic latency, and the random.seed makes the demo reproducible for testing.
Common mistakes
- Not handling failures during compensation, which can leave the system in an inconsistent state
- Forgetting to store transaction context (like order_id) needed for compensation actions
- Assuming all steps are idempotent when compensating; duplicate compensations may cause issues
Variations
- Use a saga execution engine library (e.g., `py-saga`) to formalize the transaction steps.
- Implement the saga as a state machine with explicit states and transitions.
Real-world use cases
- Coordinating order placement across microservices (order, payment, inventory) with rollback on failure.
- Handling multi-step booking flows (flight, hotel, car) where one failure cancels all reservations.
- Managing cross-service money transfers where a failed debit triggers automatic credit reversal.
Sponsored
More from Reliability & rate limiting
- At Least Once with Idempotent Consumer in Python medium
- Build a Rate Limiter Decorator in Python easy
- Build a queue-based admission control system in Python easy
- Chaos Inject Random Failures in Python easy
- Circuit breaker failure threshold count in Python medium
- Exactly Once Processing Dedupe Mock in Python easy
Keep learning
Related tutorials and quizzes for this topic.