LogoMasst Docs

CQRS

Understanding Command Query Responsibility Segregation pattern.

What is CQRS?

CQRS (Command Query Responsibility Segregation) separates read and write operations into different models. Commands (writes) and Queries (reads) use different data models optimized for their specific purpose.


Traditional vs CQRS

Traditional:
┌────────┐     ┌─────────────┐     ┌──────────┐
│ Client │────►│   Service   │────►│ Database │
└────────┘     │ (CRUD model)│     │ (single) │
               └─────────────┘     └──────────┘

CQRS:
┌────────┐     ┌─────────────┐     ┌──────────┐
│ Client │────►│   Command   │────►│  Write   │
│ (write)│     │   Service   │     │   DB     │
└────────┘     └─────────────┘     └────┬─────┘
                                        │ sync
┌────────┐     ┌─────────────┐     ┌────▼─────┐
│ Client │────►│   Query     │────►│  Read    │
│ (read) │     │   Service   │     │   DB     │
└────────┘     └─────────────┘     └──────────┘

Key Concepts

ConceptDescription
CommandWrite operation (create, update, delete)
QueryRead operation (no side effects)
Write ModelOptimized for writes (normalized)
Read ModelOptimized for reads (denormalized)

Why CQRS?

BenefitDescription
Optimized modelsEach optimized for its purpose
ScalabilityScale reads and writes independently
PerformanceRead model can be denormalized
FlexibilityDifferent storage technologies
Complexity isolationSeparate concerns

Implementation Patterns

Simple CQRS

Same database, different models:

Write ──► Normalized tables
Read  ──► Views/materialized views

Separate Databases

Different databases for read/write:

Write ──► PostgreSQL (normalized)

         └── Event/CDC ──► Read Model sync

Read  ◄────────────────────┘ Elasticsearch (denormalized)

Synchronization Strategies

StrategyConsistencyLatency
SynchronousStrongHigher write latency
AsynchronousEventualLower write latency
Event-basedEventualDecoupled

Code Example

// Command side
class OrderCommandService {
  async createOrder(command) {
    const order = new Order(command);
    await this.orderRepository.save(order);
    await this.eventBus.publish(new OrderCreatedEvent(order));
  }
}

// Query side
class OrderQueryService {
  async getOrderSummary(orderId) {
    // Read from denormalized read model
    return await this.readModelRepository.findById(orderId);
  }
}

// Event handler to sync read model
class OrderProjection {
  async handle(event: OrderCreatedEvent) {
    await this.readModel.create({
      orderId: event.orderId,
      customerName: event.customerName,
      totalAmount: event.totalAmount,
      itemCount: event.items.length
    });
  }
}

When to Use CQRS

Good fit:

  • Read/write ratio is very different
  • Complex queries on read side
  • Need to scale reads independently
  • Different teams for read/write
  • Using event sourcing

Avoid when:

  • Simple CRUD application
  • Single model suffices
  • Team unfamiliar with pattern
  • Overhead not justified

Interview Tips

  • Explain separation of commands and queries
  • Discuss eventual consistency trade-off
  • Know synchronization strategies
  • Mention combination with event sourcing
  • Give use cases: reporting, dashboards, analytics