Ultimate CQRS Pattern Cheat Sheet: Command Query Responsibility Segregation

Introduction to CQRS

Command Query Responsibility Segregation (CQRS) is an architectural pattern that separates read and write operations into distinct models. Unlike traditional architectures where the same data model handles both commands (writes) and queries (reads), CQRS uses separate models optimized for their specific purposes. This separation allows for independent scaling, specialized optimization, and better alignment with complex domain requirements. CQRS is particularly valuable for high-performance applications, complex domains with intricate business rules, and systems requiring advanced scalability.

Core CQRS Concepts

Command Side (Write Model)

  • Purpose: Handles state changes and enforces business rules
  • Focus: Data consistency, domain validation, business rule enforcement
  • Operation Types: Create, Update, Delete operations
  • Optimization For: Data integrity and processing complex business logic
  • Data Flow: Client → Command → Domain Model → Data Store

Query Side (Read Model)

  • Purpose: Retrieves and presents data to users/systems
  • Focus: Fast data retrieval, custom data shapes, reporting
  • Operation Types: Read operations
  • Optimization For: Performance, scalability, specialized query needs
  • Data Flow: Client → Query → Read Model → Data Store

Key CQRS Components

ComponentDescriptionResponsibility
CommandRepresents intent to change system stateCarries all data needed for state change
Command HandlerProcesses commandsValidates input, executes business logic
Domain ModelRich business modelEnforces invariants and business rules
EventRecords state changesCommunicates what happened
QueryRepresents data retrieval requestDefines filtering, sorting, pagination
Query HandlerProcesses queriesRetrieves and formats data
Read ModelDenormalized view of dataOptimized for specific query needs
Event StoreSpecialized databasePersists events for write model

CQRS Implementation Approaches

Basic CQRS

  • Description: Separate models but shared database
  • Complexity: Low
  • Synchronization: Immediate (same transaction)
  • Best For: Starting with CQRS, smaller applications
  • Persistence: Single database for both models
  • Advantages: Simpler implementation, no sync concerns
  • Disadvantages: Limited optimization opportunities

CQRS with Separate Datastores

  • Description: Different databases for read and write
  • Complexity: Medium
  • Synchronization: Via events or direct updates
  • Best For: Performance-critical applications
  • Persistence: Different technologies for read vs. write
  • Advantages: Optimized storage per model
  • Disadvantages: Eventual consistency concerns

Event-Sourced CQRS

  • Description: Events as source of truth with projections
  • Complexity: High
  • Synchronization: Via event stream
  • Best For: Complex domains, audit requirements
  • Persistence: Event store + read model databases
  • Advantages: Complete history, temporal queries
  • Disadvantages: Steeper learning curve, projection complexity

Step-by-Step CQRS Implementation Process

1. Domain Analysis

  • Identify bounded contexts in your domain
  • Determine which contexts benefit from CQRS
  • Analyze read vs. write usage patterns
  • Map command and query responsibilities

2. Command Model Implementation

  • Define commands representing user intents
  • Create command handlers with validation logic
  • Implement rich domain model with business rules
  • Set up command validation pipeline
  • Design appropriate persistence strategy

3. Query Model Implementation

  • Design read models optimized for query patterns
  • Create query objects representing data needs
  • Implement query handlers for data retrieval
  • Optimize for read performance (denormalization)
  • Implement caching strategies if needed

4. Synchronization Mechanism

  • Determine synchronization approach:
    • Direct updates
    • Event-based updates
    • Background processes
  • Implement eventual consistency handling
  • Add monitoring for synchronization issues

5. Integration

  • Create client-side infrastructure
  • Implement UI separation for commands vs. queries
  • Add error handling and retry logic
  • Implement versioning strategy

CQRS Code Examples

Command Definition (C#)

public class CreateOrderCommand : ICommand
{
    public Guid CustomerId { get; set; }
    public List<OrderItem> Items { get; set; }
    public string ShippingAddress { get; set; }
    public string PaymentMethod { get; set; }
}

Command Handler (C#)

public class CreateOrderCommandHandler : ICommandHandler<CreateOrderCommand>
{
    private readonly IOrderRepository _repository;
    
    public CreateOrderCommandHandler(IOrderRepository repository)
    {
        _repository = repository;
    }
    
    public async Task Handle(CreateOrderCommand command)
    {
        // Validate command
        if (command.Items == null || !command.Items.Any())
            throw new ValidationException("Order must contain items");
            
        // Create domain entity and apply business rules
        var order = new Order(command.CustomerId, command.Items);
        order.SetShippingAddress(command.ShippingAddress);
        order.SetPaymentMethod(command.PaymentMethod);
        
        // Persist changes
        await _repository.SaveAsync(order);
    }
}

Query Definition (C#)

public class GetCustomerOrdersQuery : IQuery<List<OrderSummaryDto>>
{
    public Guid CustomerId { get; set; }
    public DateTime? StartDate { get; set; }
    public DateTime? EndDate { get; set; }
    public int Page { get; set; } = 1;
    public int PageSize { get; set; } = 20;
}

Query Handler (C#)

public class GetCustomerOrdersQueryHandler : 
    IQueryHandler<GetCustomerOrdersQuery, List<OrderSummaryDto>>
{
    private readonly IOrderReadDbContext _readDb;
    
    public GetCustomerOrdersQueryHandler(IOrderReadDbContext readDb)
    {
        _readDb = readDb;
    }
    
    public async Task<List<OrderSummaryDto>> Handle(GetCustomerOrdersQuery query)
    {
        // Direct query against read-optimized model
        var orders = await _readDb.OrderSummaries
            .Where(o => o.CustomerId == query.CustomerId)
            .WhereIf(query.StartDate.HasValue, 
                o => o.OrderDate >= query.StartDate.Value)
            .WhereIf(query.EndDate.HasValue, 
                o => o.OrderDate <= query.EndDate.Value)
            .OrderByDescending(o => o.OrderDate)
            .Skip((query.Page - 1) * query.PageSize)
            .Take(query.PageSize)
            .ToListAsync();
            
        return orders;
    }
}

Event-Based Synchronization (C#)

public class OrderCreatedEventHandler : IEventHandler<OrderCreatedEvent>
{
    private readonly IOrderReadDbContext _readDb;
    
    public OrderCreatedEventHandler(IOrderReadDbContext readDb)
    {
        _readDb = readDb;
    }
    
    public async Task Handle(OrderCreatedEvent @event)
    {
        // Update read model when write model changes
        var orderSummary = new OrderSummaryReadModel
        {
            Id = @event.OrderId,
            CustomerId = @event.CustomerId,
            TotalAmount = @event.Items.Sum(i => i.Price * i.Quantity),
            ItemCount = @event.Items.Count,
            OrderDate = @event.Timestamp,
            Status = "Pending"
        };
        
        await _readDb.OrderSummaries.AddAsync(orderSummary);
        await _readDb.SaveChangesAsync();
    }
}

Comparison: Traditional vs. CQRS Architecture

AspectTraditional ArchitectureCQRS Architecture
Data ModelSingle model for reads and writesSeparate models for commands and queries
OptimizationCompromise between read and write needsEach model optimized for its purpose
ComplexityGenerally simplerMore complex, more components
ScalabilityLimited by unified modelIndependent scaling of read and write sides
ConsistencyStrong consistency by defaultCan leverage eventual consistency
PerformanceLimited optimization potentialHighly optimizable for specific patterns
MaintenanceSimpler to maintainMore moving parts to maintain
Team OrganizationSingle team typically owns entire modelCan separate responsibilities by model
CachingLimited by write concernsRead model can be aggressively cached

Common CQRS Challenges and Solutions

ChallengeSolution
Increased complexityStart small, apply CQRS only where beneficial
Eventual consistencyImplement UI feedback for pending changes
Data duplicationAccept duplication as a tradeoff for performance
Synchronization issuesImplement reliable messaging and error handling
Learning curveInvest in team training, start with simpler implementations
Over-engineeringApply CQRS selectively based on actual needs
Multiple databases managementUse infrastructure as code, automated deployments
Versioning complexityImplement clear versioning strategy for commands and events
Transaction boundariesUse sagas/process managers for distributed transactions
Development frictionCreate good tooling and templates for teams

Best Practices for CQRS Implementation

Command Side

  • Keep commands intention-revealing and named using ubiquitous language
  • Validate commands before processing
  • Design rich domain models that enforce business invariants
  • Use value objects for validation and encapsulation
  • Consider immutability for domain events
  • Implement idempotent command handlers

Query Side

  • Denormalize data for common query patterns
  • Use projection libraries for complex transformations
  • Implement caching strategies (memory, distributed, materialized views)
  • Consider read-through caches for frequently accessed data
  • Use pagination for large result sets
  • Design query-specific DTOs

General CQRS Practices

  • Don’t apply CQRS everywhere – use where it adds value
  • Start with logical separation before physical separation
  • Use a mediator pattern to decouple handlers from client code
  • Consider using CQRS frameworks to reduce boilerplate
  • Implement comprehensive monitoring and telemetry
  • Plan for eventual consistency in the UI
  • Design clear error handling strategies
  • Test commands and queries independently

CQRS with Different Tech Stacks

Event Sourcing Technologies

  • EventStoreDB
  • Axon Framework
  • NEventStore
  • Marten (PostgreSQL-based)
  • Chronicle (Java)

Read Model Technologies

  • Redis (for caching and fast reads)
  • Elasticsearch (for complex searching)
  • MongoDB (for document-based queries)
  • PostgreSQL with materialized views
  • Azure Cosmos DB (for global distribution)

Message Bus Options

  • RabbitMQ
  • Apache Kafka
  • Azure Service Bus
  • Amazon SQS/SNS
  • NATS

When to Use (and Not Use) CQRS

Good Candidates for CQRS

  • Systems with significant disparity between read and write workloads
  • Complex domains with rich business rules
  • High-performance applications requiring specialized optimization
  • Applications needing separate scaling for reads vs. writes
  • Systems requiring specialized security for commands vs. queries
  • Applications with complex reporting requirements

Poor Candidates for CQRS

  • Simple CRUD applications
  • Applications with balanced read/write patterns
  • Systems where strong consistency is a strict requirement
  • Small applications with limited domain complexity
  • Projects with tight timelines and limited resources
  • Teams new to domain-driven design concepts

Resources for Further Learning

Books

  • “Implementing Domain-Driven Design” by Vaughn Vernon
  • “CQRS Documents” by Greg Young
  • “Domain-Driven Design Distilled” by Vaughn Vernon
  • “Patterns, Principles, and Practices of Domain-Driven Design” by Scott Millett

Online Resources

  • Martin Fowler’s article on CQRS
  • Greg Young’s blog and presentations
  • Microsoft’s CQRS Journey documentation
  • Axon Framework documentation
  • EventStore documentation and patterns

Communities

  • Domain-Driven Design Community
  • CQRS and Event Sourcing Slack channels
  • Stack Overflow CQRS tag
  • GitHub repositories with CQRS examples

Remember that CQRS is not an all-or-nothing architecture. Many successful implementations apply CQRS principles selectively to parts of the system where they provide the most benefit, while keeping simpler CRUD approaches for less complex areas.

Scroll to Top