| 1 | The Problem It Solves | 2 | Pattern Structure |
| 3 | When to Use | 4 | When Not to Use |
| 5 | Trade-offs | 6 | Implementation Approach |
| 7 | Anti-Patterns to Avoid | 8 | References |
The Problem It Solves
A single data model that serves both reads and writes creates pressure in both directions. The write model must enforce business invariants — it needs consistency and transactional integrity. The read model must support flexible, high-performance queries — it needs denormalised views tailored to UI requirements. Trying to serve both with the same schema produces a model that does neither well. Complex joins slow writes; normalisation makes reads expensive.
Pattern Structure
%%{init:{'theme':'base','themeVariables':{'fontSize':'14px','fontFamily':'Inter, system-ui, sans-serif','primaryColor':'#DBEAFE','primaryTextColor':'#1e3a5f','primaryBorderColor':'#2563EB','lineColor':'#374151','clusterBkg':'#F9FAFB','clusterBorder':'#D1D5DB','edgeLabelBackground':'#FFFFFF'},'flowchart':{'curve':'orthogonal','padding':30,'nodeSpacing':65,'rankSpacing':75,'useMaxWidth':true}}}%% flowchart TD CLIENT_C[Client Application] CLIENT_C --> CMD_TYPE{Operation Type} CMD_TYPE -->|Mutation| CMD[Command\nPlaceOrder, CancelOrder\nUpdateProfile] CMD_TYPE -->|Read| QRY[Query\nGetOrderHistory\nSearchProducts] CMD --> HANDLER[Command Handler\nValidate business rules\nEnforce invariants] HANDLER --> WRITE_DB[Write Model — Database\nNormalised, consistent\nACID transactions] WRITE_DB --> EVENT_C[Publish domain event\nOrderPlaced, OrderCancelled] EVENT_C --> PROJECTOR[Event Projector\nBuilds read model\nDenormalised views] PROJECTOR --> READ_DB[Read Model — Database\nDenormalised, optimised\nPer-query view] QRY --> READ_HANDLER[Query Handler\nNo business logic\nData retrieval only] READ_HANDLER --> READ_DB READ_DB --> RESULT([Query Result Returned]) style CLIENT_C fill:#4f8ef7,color:#fff style RESULT fill:#10b981,color:#fff style PROJECTOR fill:#e0f2fe style EVENT_C fill:#e0f2fe
When to Use
- Systems where read and write patterns are fundamentally different in shape or scale
- Domains with complex business rules that are hard to enforce alongside complex query requirements
- Applications where read throughput significantly exceeds write throughput — read model can be scaled independently
- Systems already using event sourcing — CQRS is a natural companion
- Collaborative domains where multiple users modify the same aggregate concurrently
When Not to Use
- Simple CRUD applications where the same data shape serves both reads and writes
- Small-scale systems where the consistency window introduced by the read model projection is not acceptable
- Teams without experience building and maintaining two separate data models
- Domains with low data volume where a single well-indexed database handles both concerns efficiently
Trade-offs
| Benefit | Cost |
|---|---|
| Read and write sides scale independently | Two data models to design, build, and maintain |
| Read model optimised per query — no joins at read time | Eventual consistency between write and read models |
| Write model focuses solely on business invariants | Event projectors add operational complexity |
| Simpler command handlers — one concern each | Debugging spans write model, event bus, and read model |
Implementation Approach
Define commands and queries as distinct types. Commands express intent: PlaceOrder, not SaveOrder. Queries are named for what they return: GetOrderSummary, not GetOrder. This distinction at the type level makes the segregation explicit in code.
Keep command handlers focused on invariants. A command handler validates the command, loads the aggregate, applies the business rule, saves the result, and publishes a domain event. Nothing else. No query logic, no read model concerns.
Build read model projections from events. When a domain event is published, a projector updates one or more read model tables. The projector denormalises — it joins data from multiple domain events into a single query-optimised view. If the read model becomes stale or corrupted, it can be rebuilt by replaying the event stream.
Accept eventual consistency in the read model. After a command completes, the read model may not reflect the change for a brief period while the projection processes the event. Design the UI to handle this — optimistic updates, loading states, or polling for confirmation are all valid strategies.
Anti-Patterns to Avoid
Applying CQRS to a straightforward resource management screen — create user, update profile, list users — where the write and read shapes are identical. Two models, two databases, event projectors, and eventual consistency for a feature that a single database table and a REST controller would serve perfectly.
Apply CQRS where the write and read concerns genuinely diverge. A useful test: if the read model would look identical to the write model, CQRS adds cost without benefit.
Putting business rule validation or state mutation into query handlers. Queries should return data and nothing else. When a query has side effects, the distinction between commands and queries breaks down and the pattern's benefits are lost.
Query handlers load from the read model and return. Any operation that changes state is a command. Command-query separation — no side effects in queries — is the non-negotiable invariant of CQRS.
Flowchart
References
- Evans, Eric — Domain-Driven Design. Addison-Wesley, 2003.
- Young, Greg — CQRS Documents. cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf
- Fowler, Martin — CQRS. martinfowler.com/bliki/CQRS
- Microsoft — CQRS Pattern. learn.microsoft.com/en-us/azure/architecture/patterns/cqrs