Architecture

CQRS

Separating the read and write models of a system to optimise each independently.

Overview

Command Query Responsibility Segregation (CQRS) separates the write model (commands that change state) from the read model (queries that return data). Write and read models can use different data structures, different databases, and scale independently. Queries never produce side effects; commands never return data.

Origin

CQRS emerged from Greg Young's work around 2010, building on Bertrand Meyer's Command-Query Separation principle (CQS, 1988). CQS applies at the method level; CQRS applies at the system architecture level.

Examples

CQRS with separate read and write models

// Write side: normalised, enforces invariants
class PlaceOrderCommand {
  async execute({ customerId, items }) {
    const customer = await this.customerRepo.find(customerId)
    const order    = Order.create({ customer, items })  // enforces business rules
    await this.orderRepo.save(order)
    this.events.emit('order:placed', order.toDomainEvent())
  }
}

// Read side: denormalised, optimised for query patterns
// Populated by event handlers, can be a Redis cache, Elasticsearch, or a separate Postgres schema
class OrderReadModel {
  // Flat, pre-joined view: no N+1, no joins at query time
  async findByCustomer(customerId) {
    return this.readDb.query(
      'SELECT * FROM orders_view WHERE customer_id = $1 ORDER BY placed_at DESC',
      [customerId]
    )
  }
}

// Event handler updates the read model asynchronously
events.on('order:placed', async (event) => {
  await readDb.query(
    'INSERT INTO orders_view (id, customer_id, customer_name, total, status, placed_at) VALUES ($1,$2,$3,$4,$5,$6)',
    [event.id, event.customerId, event.customerName, event.total, 'pending', event.placedAt]
  )
})

Use Cases

  • 01Read-heavy systems where denormalised read models dramatically outperform normalised writes
  • 02Different scaling needs: reads scale horizontally; writes need strict consistency
  • 03Complex queries that would require expensive joins on the write model
  • 04Audit and reporting requirements from the same data as transactional writes

When Not to Use

  • //Simple applications where read and write loads are comparable and the data model is straightforward
  • //Systems that require strong read-after-write consistency, CQRS introduces eventual consistency
  • //Small teams: the operational overhead of maintaining two data models requires discipline

Technical Notes

  • CQRS does not require Event Sourcing, though they pair naturally. CQRS can use traditional CRUD on the write side with a separate read projection
  • Eventual consistency between write and read models must be designed for: the UI may show stale data briefly after a command
  • The read model is disposable, if the read schema changes, delete it and rebuild from the event log or write database
  • GraphQL naturally represents the query side of CQRS; REST mutations are the command side