A demonstration of the Saga Choreography pattern for managing distributed transactions across microservices using Apache Kafka for event-driven communication and MySQL for data persistence.
The Saga Pattern manages distributed transactions without distributed locks by breaking them into local transactions that communicate via events.
In Choreography, there is no central coordinator. Each service:
- Listens for events it cares about
- Performs its local transaction
- Publishes events for other services to react to
- Handles compensation (rollback) when it receives failure events
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Order Service │ │Inventory Service│ │ Payment Service │
│ │ │ │ │ │
│ Creates orders │ │ Reserves stock │ │ Processes pay- │
│ Tracks status │ │ Releases stock │ │ ment │
│ │ │ (compensation) │ │ │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
└───────────────────────┴───────────────────────┘
│
┌──────────────────┴──────────────────┐
│ KAFKA │
│ │
│ Topics: │
│ - order.created │
│ - inventory.reserved │
│ - inventory.released │
│ - payment.completed │
│ - payment.failed │
└──────────────────┬──────────────────┘
│
┌──────────────────┴──────────────────┐
│ MYSQL │
│ │
│ Tables: │
│ - orders │
│ - inventory │
│ - inventory_reservations │
│ - payments │
└─────────────────────────────────────┘
1. Order Service → creates order (PENDING) → publishes order.created
2. Inventory Service → reserves stock → publishes inventory.reserved
3. Payment Service → processes payment → publishes payment.completed
4. Order Service → marks order CONFIRMED
1. Order Service → creates order (PENDING) → publishes order.created
2. Inventory Service → reserves stock → publishes inventory.reserved
3. Payment Service → FAILS → publishes payment.failed
4. Inventory Service → COMPENSATION: releases stock → publishes inventory.released
5. Order Service → marks order FAILED
docker-compose up -dWait for MySQL to initialize (~15 seconds). Check with:
docker-compose logs saga-mysqlpython3 -m venv venv
source venv/bin/activate
pip install -r requirements.txtpython api.pyCreate a successful order (< $100):
curl -X POST http://localhost:5001/orders \
-H "Content-Type: application/json" \
-d '{"customer_id": "CUST-001", "product_id": "PROD-001", "quantity": 2, "total_amount": 50.0}'Create a failing order (>= $100) to trigger compensation:
curl -X POST http://localhost:5001/orders \
-H "Content-Type: application/json" \
-d '{"customer_id": "CUST-002", "product_id": "PROD-001", "quantity": 5, "total_amount": 150.0}'Check results:
# List all orders
curl http://localhost:5001/orders
# Check inventory levels
curl http://localhost:5001/inventory
# Check reservations (see RELEASED status for failed orders)
curl http://localhost:5001/inventory/reservations
# Check payments
curl http://localhost:5001/payments# Check all tables
docker exec saga-mysql mysql -usaga_user -psaga_password saga_db -e "
SELECT '=== ORDERS ===' AS '';
SELECT order_id, total_amount, status, failure_reason FROM orders;
SELECT '=== INVENTORY ===' AS '';
SELECT * FROM inventory;
SELECT '=== RESERVATIONS ===' AS '';
SELECT reservation_id, order_id, quantity, status FROM inventory_reservations;
SELECT '=== PAYMENTS ===' AS '';
SELECT payment_id, order_id, amount, status FROM payments;
"python run_example.pyThis runs all three scenarios automatically with detailed output.
Open http://localhost:8081 to see topics and messages.
| Method | Endpoint | Description |
|---|---|---|
| POST | /orders |
Create new order |
| GET | /orders/<id> |
Get order status |
| GET | /orders |
List all orders |
| GET | /inventory |
Check stock levels |
| GET | /inventory/reservations |
Check reservations |
| GET | /payments |
List all payments |
| GET | /health |
Health check |
The payment service is configured to fail orders with total_amount >= $100.
| Amount | Result |
|---|---|
| < $100 | Payment SUCCESS → Order CONFIRMED |
| >= $100 | Payment FAILED → COMPENSATION → Order FAILED |
orders
- order_id, customer_id, product_id, quantity, total_amount, status, failure_reason
inventory
- product_id, product_name, quantity
inventory_reservations
- reservation_id, order_id, product_id, quantity, status (RESERVED/RELEASED/CONFIRMED)
payments
- payment_id, order_id, amount, status (COMPLETED/FAILED), failure_reason
| File | Description |
|---|---|
docker-compose.yml |
Kafka + Zookeeper + MySQL + Kafka UI |
init.sql |
Database schema and initial data |
config.py |
Kafka configuration and topic names |
database.py |
MySQL connection pool |
events.py |
Event definitions (dataclasses) |
order_service.py |
Creates orders, tracks completion/failure |
inventory_service.py |
Reserves/releases stock with compensation |
payment_service.py |
Processes payments, simulates failures |
api.py |
REST API (Flask) |
run_example.py |
CLI demo script |
| Service | Port |
|---|---|
| Flask API | 5001 |
| Kafka | 9092 |
| MySQL | 3307 |
| Kafka UI | 8081 |
| Zookeeper | 2181 |
- Event-Driven Communication: Services communicate only via Kafka events
- Decoupled Services: No service calls another directly
- Compensation: Inventory is automatically released when payment fails
- Eventual Consistency: System reaches consistent state through event processing
- Data Persistence: All state changes are persisted in MySQL
docker-compose down -v