Saga pattern orchestration with rollback in Python
Orchestrate a distributed transaction with Saga steps and automated compensation rollback on failure.
Python code
57 linesimport time
import random
class SagaStep:
def __init__(self, name):
self.name = name
self.executed = False
def execute(self):
print(f"Executing {self.name}...")
time.sleep(0.2)
if random.random() < 0.3:
raise RuntimeError(f"{self.name} failed")
self.executed = True
print(f"{self.name} succeeded")
def compensate(self):
print(f"Compensating {self.name}...")
class SagaOrchestrator:
def __init__(self, steps):
self.steps = steps
self.executed_steps = []
def run(self):
for step in self.steps:
try:
step.execute()
self.executed_steps.append(step)
except Exception as e:
print(f"ERROR: {e}")
self._rollback()
return False
print("Saga completed successfully")
return True
def _rollback(self):
print("Rolling back saga...")
for step in reversed(self.executed_steps):
step.compensate()
def main():
random.seed(42)
steps = [
SagaStep("Reserve Inventory"),
SagaStep("Process Payment"),
SagaStep("Ship Order"),
]
orchestrator = SagaOrchestrator(steps)
orchestrator.run()
if __name__ == "__main__":
main()
Output
Executing Reserve Inventory...
Reserve Inventory succeeded
Executing Process Payment...
Process Payment succeeded
Executing Ship Order...
Ship Order succeeded
Saga completed successfully
How it works
The Saga orchestrator sequentially executes each step, tracking completed steps. If any step raises an exception, the orchestrator triggers compensation in reverse order for all previously successful steps. This ensures atomicity at the application level without distributed locks. Using a simple list of executed steps allows clean rollback semantics. Random failures illustrate how compensation restores consistency.
Common mistakes
- Not compensating in reverse order of execution
- Forgetting to track steps that actually executed
- Handling failures inside the step instead of letting the orchestrator catch them
- Assuming compensation must be synchronous with the failure
Variations
- Use a database table to persist saga state for crash recovery
- Chain compensation functions as a decorator-based pipeline
Real-world use cases
- E-commerce order placement across inventory, payment, and shipping services.
- Banking transfers where locking an account must be reversed if a credit fails.
- Multi-service user onboarding with rollback on any registration step failure.
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.