Özer Cem KELAHMETKELAHMET
Tools · Architecture

CQRS

CQRS (Command Query Responsibility Segregation) uses separate models for updates and reads, so each can be optimised. Learn when it helps and what it costs.

Overview

CQRS stands for Command Query Responsibility Segregation. It is an architectural pattern that uses one model for changing data, the command or write side, and a different model for reading data, the query or read side. Instead of one set of classes and one database schema serving every screen and every update, the write model is shaped to enforce business rules, and one or more read models are shaped to answer specific questions quickly.

An everyday analogy is a library. Returning and borrowing books happens at the front desk, where staff check membership, due dates and fines; this is the careful write side, with rules. But readers do not ask the front desk for every question. They use the catalogue, a separate index organised by author, title and subject, built purely for finding things fast. The catalogue is updated after each new book arrives, so it may lag for a moment, but nobody minds. CQRS gives software the same split between the counter that enforces rules and the catalogue built for lookups.

CQRS is not a whole architecture and is not needed in most systems. It helps where reads and writes have very different shapes or loads, where the domain logic on the write side is complex, or where many different views of the same data are needed. It often appears with event sourcing and domain-driven design, but it can be used without either.

What it is

CQRS separates responsibilities into a few parts.

Part Role
Command A request to change state, named in business terms, such as PlaceOrder or CancelBooking
Command handler Validates the command, loads the write model, applies rules and saves changes
Write model The domain model optimised for correctness and invariants, often aggregates
Event or change notification A record that the write side changed, used to update read models
Projection Code that turns changes into one or more read-optimised views
Read model Denormalised data shaped for a particular query or screen
Query A request for data that never changes state

Commands return little or nothing beyond success, failure or an identifier. Queries return data and have no side effects. The two sides may share one database with different tables or use separate stores, such as a relational database for writes and a search index or cache for reads.

Where it came from

CQRS builds on command-query separation (CQS), a principle Bertrand Meyer described in his book "Object-Oriented Software Construction" in 1988: a method should either change state or return a value, but not both. CQRS lifts that principle from individual methods to the level of whole models.

Greg Young named and popularised CQRS around 2010 in talks and writing, with Udi Dahan and others contributing to its early explanation, often in the context of domain-driven design and event sourcing. Martin Fowler published a widely read short article on it in 2011, which also warned that it adds risky complexity for most systems. The pattern later appeared in cloud architecture guidance from major vendors and in the toolkits of event-sourcing frameworks.

How it works

1. Receive a command

A client sends a command such as ReserveSeat(flight=TK1, seat=12A, passenger=P42). The command is an intent, not a data update.

2. Validate and apply on the write side

The command handler loads the relevant aggregate, checks rules (is the seat free, is the booking still open), changes state and saves it in a transaction. If rules fail, the command is rejected.

3. Publish the change

After saving, the write side emits a change notification or domain event, such as SeatReserved. To avoid losing events, systems often use the outbox pattern: the event is written to an outbox table in the same transaction and then published to a broker such as Apache Kafka or RabbitMQ.

4. Update projections

Projection handlers consume events and update read models. One read model might be a seat map per flight in Redis; another might be a passenger itinerary document; another a search index in Elasticsearch.

5. Serve queries

Queries read directly from the read models, with no joins or business logic. Each read model is shaped for its screen.

6. Live with eventual consistency

Because projections update after the write, a read may briefly show old data. Clients handle this by showing the command's own result, polling, or waiting for a version number to appear in the read model.

Key concepts and terms

Term Meaning
CQS Principle that a method either commands or queries, not both.
Command An intention to change state, which may be rejected.
Query A side-effect-free request for data.
Write model The model that enforces rules for changes.
Read model A query-optimised, often denormalised view.
Projection Logic that builds read models from changes or events.
Eventual consistency Read models catch up with writes after a delay.
Outbox pattern Storing events in the same transaction as state, then publishing them.
Idempotent handler A handler that gives the same result if a message is processed twice.
Rebuild Recreating a read model by replaying events or recomputing from source.
Materialised view Precomputed query results stored for fast access.

A worked example

This scenario is illustrative. An online marketplace has 2 million product listings. Sellers update prices and stock a few hundred times per minute. Buyers run about 15,000 searches per minute, filtering by category, price, rating and delivery time. The single relational schema serves both, and search queries join six tables. At peak, writes time out because the database is busy with reads.

The team applies CQRS to the catalogue only. The write side keeps a normalised relational model where commands such as ChangePrice and AdjustStock are validated: price must be positive, stock cannot go below reserved quantity. Each change writes an event to an outbox table in the same transaction. A relay publishes events to Kafka.

Two projections consume them. The first updates a search index document per listing with price, stock flag, rating and delivery estimate. The second updates a per-seller dashboard table with counts and revenue. Search now hits the index only, and p95 search latency drops from 1.2 seconds to 90 ms. Writes stop timing out. The cost is visible too: a price change typically appears in search within 1 to 2 seconds rather than instantly, and the team adds monitoring for projection lag, an alert at 30 seconds, and a script to rebuild the index from the write database if a projection bug corrupts it.

Strengths and limitations

Strengths:

  • Reads and writes can be optimised, scaled and stored independently.
  • Write models stay focused on business rules without query concerns.
  • Many tailored read models can serve different screens and consumers.
  • Read side can scale horizontally with caches and replicas.
  • Fits naturally with event sourcing and event-driven integration.

Limitations:

  • More moving parts: messaging, projections, multiple stores.
  • Eventual consistency complicates user experience and testing.
  • Projection bugs can silently produce wrong views until rebuilt.
  • Duplicate data requires care with privacy and deletion requests.
  • Overkill for simple CRUD where one model serves both sides well.

How it compares with alternatives

Approach Description When it fits better
Single CRUD model One model and schema for reads and writes Most applications with modest load and simple rules
Read replicas Database copies serving reads with the same schema Read scaling without different query shapes
Materialised views in the database The database maintains precomputed query results Moderate needs within one database engine
Event sourcing Stores events as the source of truth Often combined with CQRS when full history matters
GraphQL over services Flexible query layer over existing APIs Client flexibility rather than separate storage

Practical guidance and best practices

  1. Apply CQRS to specific bounded contexts or features where it pays off, not to the whole system.
  2. Name commands in business language and keep them as intentions that can be rejected.
  3. Use the outbox pattern so state changes and published events never diverge.
  4. Make projection handlers idempotent and track the last processed position.
  5. Design each read model for one screen or use case; add new ones rather than overloading one.
  6. Monitor projection lag and alert when it exceeds what users can tolerate.
  7. Be able to rebuild any read model from the source of truth, and test the rebuild.
  8. Include all read models in data-retention and deletion processes for personal data.

Common mistakes

  • Introducing CQRS everywhere because it sounds scalable, multiplying complexity.
  • Publishing events after the database commit without an outbox, losing events on crashes.
  • Letting queries read from the write model "just this once", recreating coupling.
  • Ignoring eventual consistency in the interface, confusing users who do not see their own changes.
  • Writing non-idempotent projections that double-count when messages are redelivered.
  • Forgetting that read models also hold personal data subject to GDPR.

Frequently asked questions

Does CQRS require two databases?

No. The separation is between models. Both can live in one database with different tables or views. Separate stores are an option when read and write needs differ greatly.

Is CQRS the same as event sourcing?

No. CQRS separates reads from writes. Event sourcing stores state as a sequence of events. They combine well, because events are a natural way to feed projections, but each can be used alone.

How do users see their own changes immediately?

Common techniques are returning the new state in the command response, waiting until the read model reaches the command's version, or showing an optimistic update in the interface.

When should I avoid CQRS?

When the domain is simple, reads and writes look similar, load is modest, and the team has no experience running messaging and projections. A single model is easier to build and operate.

How do I fix a broken read model?

Fix the projection code, then rebuild the read model from the source of truth, either by replaying events or by recomputing from the write database.

Can CQRS help AI applications?

Sometimes. Feature stores and search indexes for retrieval are, in effect, read models built from operational data, and the same discipline of projections, lag monitoring and rebuilds applies.

Glossary

  • Command: a request to change state.
  • Query: a request to read state.
  • Read model: a view shaped for queries.
  • Write model: the rule-enforcing model for updates.
  • Projection: code that builds read models.
  • Outbox: a table holding events written in the same transaction as state.
  • Eventual consistency: temporary disagreement that resolves over time.
  • Idempotency: processing a message twice has the same effect as once.
  • Denormalisation: duplicating data to avoid joins at read time.

Summary

CQRS separates the model that changes data from the models that read it, so each can be designed and scaled for its job. It extends Bertrand Meyer's command-query separation and was named and popularised by Greg Young around 2010. It works well where reads and writes differ sharply or the domain is complex, often alongside event sourcing and domain-driven design. It brings eventual consistency, extra infrastructure and projection maintenance, so use it selectively, publish changes reliably with an outbox, keep projections idempotent and rebuildable, and monitor lag.

It belongs to Architecture in my stack, under MLOps and AI infrastructure. Others in the same group:

Type to search.
    ↑↓ navigate↵ open