Marcelo Pastorino, Software Developer

CQRS

Separating commands that change state from queries that read it, with optional distinct write and read models.

CQRS (command query responsibility segregation) separates the paths that change state from the paths that read it. A command expresses intent to do something: place an order, capture payment, cancel a shipment. A query asks for a view: order status, invoice history, search results.

The split starts as an interface rule. In larger systems it often becomes two models: a write model optimized for business rules and consistency, and one or more read models optimized for how data is displayed.

Commands vs queries

Command Query
Purpose Change state Return data
Side effects Yes No
Return value Often an ID or ack, not a full view The requested view
Validation Business rules and invariants Access rules and filters
Failure mode Reject the change Return empty or error

Queries should not mutate state as a side effect. Commands should not be used as a disguised read when a dedicated query would be clearer.

Simple vs full CQRS

Simple CQRS keeps one database and one domain model, but routes writes and reads through different application entry points. The benefit is clearer code and safer APIs without new infrastructure.

Full CQRS uses separate storage or schemas for writes and reads. The write side commits authoritative state. The read side is rebuilt from events, projections, or replication. Reads may lag behind writes, so the system is often eventually consistent.

Full CQRS with separate write and read paths

Full CQRS is a scaling and modeling choice, not a default architecture.

Write model and read model

The write model enforces invariants, owns transactions, and records what happened. It is usually normalized, aggregate-oriented, and aligned with domain-driven design inside a bounded context. Event sourcing is one way to implement that write model as an append-only event history.

The read model is shaped for screens, reports, or APIs. It may denormalize data, precompute joins, or index fields the write model does not optimize for. Multiple read models can exist for the same write stream when different consumers need different views.

That duplication is intentional. Integration through events is often cheaper than forcing every query through one shared schema.

How CQRS fits distributed systems

CQRS pairs naturally with asynchronous integration:

  • The write side commits local state and publishes facts through the outbox pattern or a broker.
  • Projectors, search indexes, caches, and dashboards consume those facts and update read models.
  • Consumers use the inbox pattern and idempotency because delivery is usually at-least-once.

This is the same shape as event-driven architecture at scale. Commands become messages with one owner. Queries stay local to the read store that already matches the question.

Relationship to the command pattern

The command pattern encapsulates a request as an object in a single process. CQRS applies the same idea across application boundaries and, in full form, across data stores.

In-process command objects help with undo, logging, and uniform dispatch. Distributed command handlers help with ownership, scaling writes, and keeping read traffic off the transactional path.

When to use it

CQRS helps when:

  • Read and write workloads have very different scale or shape.
  • The UI needs denormalized views that would complicate the write model.
  • Multiple teams or bounded contexts need different projections of the same facts.
  • You already operate event-driven architecture and need explicit read-side rebuild paths.

Start with simple CQRS. Promote to separate models only when measured pain justifies the operational cost.

When to skip it

Avoid full CQRS when:

  • The application is small and CRUD clarity is enough.
  • Every read must reflect the latest write immediately across all views.
  • The team cannot own projection lag, replay, and schema drift on the read side.
  • The problem is a handful of endpoints, not diverging access patterns.

A single database with clear service methods is often the right answer.

Trade-offs

  • Full CQRS adds projectors, monitoring, and failure modes around stale reads.
  • Debugging spans write commits, event publication, and read rebuilds.
  • Simple CQRS gives most of the clarity benefit with far less infrastructure.
  • CQRS complements DDD and EDA, but it does not replace them. Boundaries, language, and delivery guarantees still need explicit design.

See also

  • Event-Driven Architecture, Decoupling producers and consumers through asynchronous messaging.
  • Outbox Pattern, Reliably publishing events by committing them in the same transaction as state changes.
  • Domain-Driven Design, Modeling software around the business domain, bounded language, and explicit context boundaries.
  • Eventual Consistency, A consistency model where replicas converge to the same state over time, allowing temporary divergence after a write.
  • Command Pattern, A behavioral design pattern that encapsulates a request as an object, so callers can queue, log, undo, or defer execution without knowing how the work is done.
  • Bounded Contexts, Aligning team language with explicit code and data boundaries.
  • Inbox Pattern, Deduplicating inbound messages so consumer side effects run exactly once.
  • Event Sourcing, Persisting application state as an append-only sequence of domain events instead of only the latest row values.

On Wikipedia