CQRS — Command Query Responsibility Segregation
Separate the model that changes state from the model that answers questions. Writes go through the domain; reads take the shortest path to the data a screen needs.
Rules for agents
- Commands change state, enforce invariants, and return little or nothing. They run through aggregates and repositories.
- Queries return data and change nothing. They may bypass the domain entirely — a hand-written SQL projection straight into a view DTO is correct here, not a shortcut.
- Never mix: no command returning a read model, no query with a side effect.
- Shape read models for the consumer (one per screen or endpoint). Duplication between read models is fine — they are projections, not a shared model.
- Do not distort an aggregate to make a list screen convenient.
Levels — pick the lowest that solves the problem
- Separate methods/services for reads and writes over the same database. Usually enough.
- Separate models — domain objects for writes, direct queries for reads.
- Separate stores — a denormalised read store updated by events. Only under real read-scale pressure.
When not to use this
Level 3 brings eventual consistency, and the UI must then handle stale reads. Do not take it on without a measured need (see ../principles/measure-first.md). For most applications, level 1 or 2 is the destination — not a stepping stone.
Smells
A read model rebuilt through aggregates one at a time, getOrderAndMarkAsViewed(), event sourcing adopted for a CRUD app, users confused by data that has not appeared yet.