Engineering Playbook
System Patterns

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:

  1. Command Side (Write): Optimized for consistency. Example: Users table.
  2. Query Side (Read): Optimized for speed. Example: UserDashboardView (a flattened NoSQL document).

How it works:

  1. User updates Profile (Command).
  2. System updates Users DB.
  3. System publishes UserUpdated event.
  4. 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:

  • CreateOrderCommand encapsulates user intent
  • OrderCommandHandler validates business rules
  • OrderRepository handles persistence
  • EventBus notifies 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:

  • GetOrderDetailsQuery specifies query parameters
  • OrderDetailsQueryHandler executes optimized reads
  • OrderViewRepository accesses denormalized read storage
  • OrderDetailsView includes 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

  1. Step 1: Book Flight (Success).
  2. Step 2: Book Hotel (Success).
  3. Step 3: Charge Card (Fail - Insufficient Funds).

The Rollback (Compensation)

The Saga cannot just "stop". It must undo:

  1. Undo Step 2: Cancel Hotel.
  2. Undo Step 1: Cancel Flight.
  3. 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 FlightReserved event
  • Hotel Service listens and emits HotelReserved event
  • Payment Service listens and emits either PaymentProcessed or PaymentFailed
  • 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.