Python Saga Compensating Steps Mock
Mock a distributed transaction saga with forward steps and compensating actions that reverse partial progress on failure.
Python code
64 linesfrom datetime import datetime
def make_payment(user_id, amount):
print(f"[{datetime.now():%H:%M:%S}] Payment of ${amount} processed for user {user_id}")
return {"step": "payment", "status": "ok", "details": f"${amount} charged"}
def deduct_inventory(order_id, items):
print(f"[{datetime.now():%H:%M:%S}] Inventory deducted for order {order_id}: {items}")
return {"step": "inventory", "status": "ok", "details": f"{len(items)} items reserved"}
def notify_user(user_id, order_id):
print(f"[{datetime.now():%H:%M:%S}] Notification sent to user {user_id} for order {order_id}")
return {"step": "notify", "status": "ok", "details": f"Order {order_id} confirmed"}
def compensate_payment(transaction_id):
print(f"[{datetime.now():%H:%M:%S}] COMPENSATION: Refund issued for transaction {transaction_id}")
return "refunded"
def compensate_inventory(order_id, items):
print(f"[{datetime.now():%H:%M:%S}] COMPENSATION: Inventory restored for order {order_id}: {items}")
return "restored"
def compensate_notify(user_id, order_id):
print(f"[{datetime.now():%H:%M:%S}] COMPENSATION: Cancel notification for order {order_id}")
return "cancelled"
def run_saga(user_id, order_id, items, amount, fail_at="notify"):
steps = [
("payment", make_payment, lambda: compensate_payment(transaction_id)),
("inventory", deduct_inventory, lambda: compensate_inventory(order_id, items)),
("notify", notify_user, lambda: compensate_notify(user_id, order_id)),
]
# Simulate a transaction ID for compensation demo
global transaction_id
transaction_id = f"TXN-{order_id}"
executed = []
for name, forward, compensate in steps:
if name == fail_at:
print(f"[{datetime.now():%H:%M:%S}] FAILURE at step '{name}' — simulating outage")
raise RuntimeError(f"{name} step failed")
result = forward(*(user_id, amount) if name == "payment" else
(order_id, items) if name == "inventory" else
(user_id, order_id))
executed.append((name, result, compensate))
print("Saga completed — all steps succeeded. No compensation needed.")
return True
if __name__ == "__main__":
try:
run_saga(user_id=42, order_id=1001, items=["laptop", "mouse"], amount=1299.99, fail_at="inventory")
except RuntimeError as e:
print(f"Saga aborted: {e}")
finally:
print("--- Compensation sequence finished ---")
Output
[10:15:30] Payment of $1299.99 processed for user 42
[10:15:30] Inventory deducted for order 1001: ['laptop', 'mouse']
[10:15:30] FAILURE at step 'inventory' — simulating outage
Saga aborted: inventory step failed
[10:15:30] COMPENSATION: Refund issued for transaction TXN-1001
Saga aborted: inventory step failed
--- Compensation sequence finished ---
How it works
The saga pattern coordinates multiple service operations, tracking each successful step with a compensating action. On failure, previously executed steps are rolled back in reverse order to restore consistency. Here, we simulate failure at a chosen step and trigger compensation for earlier steps. The compensate callbacks are stored with each executed step, allowing easy reversal. This mock helps developers reason about rollback logic before implementing real distributed calls.
Common mistakes
- Triggering compensation for steps not yet executed
- Forgetting global variable scope for transaction_id
- Calling compensation in forward order instead of reverse
- Assuming any step can fail without simulating the failure point
Variations
- Use a list of dicts with 'name', 'forward', and 'compensate' keys for clearer structure.
- Wrap each step in a try/except to handle real exceptions and automatically run compensation.
Real-world use cases
- Distributed order processing where payment, inventory, and shipping must all succeed or roll back.
- Booking systems that reserve a flight, hotel, and car together using saga rollback on failure.
- Cross-service account transfers where debiting and crediting must be atomic via compensation.
Sponsored
More from Microservices patterns
- BFF aggregation pattern: combine multiple service responses in Python easy
- Backward Compatible Schema Evolution in Python medium
- Bulkhead Thread Pool per Service Mock in Python medium
- CQRS with Separate Read and Write Repositories in Python medium
- Cache-Aside Pattern in Python: Per-Service Mock easy
- Consumer Driven Contract Pact Mock in Python medium
Keep learning
Related tutorials and quizzes for this topic.