CQRS & Sagas
Handling transactions and complex queries in distributed systems.
CQRS & Sagas
When you split a monolith, you lose two things: complex SQL joins and ACID transactions. CQRS and Sagas are the patterns we use to get them back.
CQRS (Command Query Responsibility Segregation)
In most apps, the data model for writing (3rd normal form, complex validation) is different from the model for reading (reporting, dashboard views).
CQRS splits them into two distinct paths:
- Command Side (Write): Optimized for consistency. Example:
Userstable. - Query Side (Read): Optimized for speed. Example:
UserDashboardView(a flattened NoSQL document).
How it works:
- User updates Profile (Command).
- System updates
UsersDB. - System publishes
UserUpdatedevent. - A separate worker consumes event and updates the
UserDashboardView(Read DB).
Trade-off: Eventual Consistency. The dashboard might be out of date for a few milliseconds.
CQRS Implementation Example: E-Commerce Order System
Write Model (Command Side)
Key Characteristics:
- Optimized for consistency and business rule validation
- Uses command handlers to process write operations
- Publishes events after successful writes
- Stores data in normalized relational format
Components:
CreateOrderCommandencapsulates user intentOrderCommandHandlervalidates business rulesOrderRepositoryhandles persistenceEventBusnotifies other systems
Read Model (Query Side)
Key Characteristics:
- Optimized for fast reads and reporting
- Uses query handlers for read operations
- Stores denormalized data for performance
- May include user-friendly fields like product names and images
Components:
GetOrderDetailsQueryspecifies query parametersOrderDetailsQueryHandlerexecutes optimized readsOrderViewRepositoryaccesses denormalized read storageOrderDetailsViewincludes all needed display data
Key Difference: Read model might include redundant data from multiple tables (user names, product images) to avoid joins during display.
Sagas: Distributed Transactions
You need to update data across Service A, B, and C. In a monolith, you use a database transaction. In microservices, that is impossible.
A Saga is a sequence of local transactions. If one fails, you must execute Compensating Transactions to undo the previous steps.
Example: Booking a Trip
- Step 1: Book Flight (Success).
- Step 2: Book Hotel (Success).
- Step 3: Charge Card (Fail - Insufficient Funds).
The Rollback (Compensation)
The Saga cannot just "stop". It must undo:
- Undo Step 2: Cancel Hotel.
- Undo Step 1: Cancel Flight.
- Result: System returns to consistent state.
Saga Implementation Patterns
Orchestration Saga Example
Central coordinator controls the flow:
- Define sequential steps: Reserve Flight → Reserve Hotel → Process Payment
- Each step specifies both action and compensation
- If any step fails, coordinator automatically executes compensations in reverse order
- Single place to visualize and manage the entire business process
Benefits:
- Clear, visible business flow
- Centralized error handling
- Easy to add monitoring and logging
- Complex branching logic handled in one place
Choreography Saga Example
Services react to events independently:
- Flight Service emits
FlightReservedevent - Hotel Service listens and emits
HotelReservedevent - Payment Service listens and emits either
PaymentProcessedorPaymentFailed - Failure events trigger compensations automatically
Benefits:
- High decoupling between services
- Each service owns its own logic
- No single point of failure
- Scales well as more services are added
Challenges:
- Hard to visualize the complete flow
- Difficult to debug across services
- Event ordering can be tricky
When to Use CQRS
Good for:
- High-read/write ratio systems (dashboards, analytics)
- Complex business rules on write side
- Different data models for reads vs writes
- Event sourcing architectures
Avoid for:
- Simple CRUD applications
- Teams new to distributed systems
- Applications requiring immediate consistency
Choreography Saga
Services listen for failure events and trigger their own undo logic. Simple, but hard to track.
Orchestration Saga
A central 'Saga Coordinator' (State Machine) tells services to execute or rollback. (e.g., AWS Step Functions, Temporal).
Design for Compensation
You cannot always "undo". If Step 1 sends an email saying "Welcome!", you can't un-send it. You have to send a second email saying "Oops, ignore that." Compensation requires business logic, not just database rollbacks.