When CQRS is overkill
CQRS buys structural separation at real cost: two models, eventual consistency, projection infra. It pays off only where read/write asymmetry is genuine. Most CRUD does not qualify. CQRS and event sourcing are independent — conflating them doubles the cost.
After the billing platform team shipped CQRS for the order domain, the pattern became the team’s default answer. The account management module needed a screen to edit company details — name, address, billing contact. A developer proposed splitting it into a CompanyAggregate write model and a CompanyProfileView read model with an async projection. The tech lead pushed back: “What invariant does the write model guard? What read/write asymmetry justifies the projection?” The developer paused. The company edit screen had no complex invariants. A company had a name, an address, and a billing contact — three fields, one table, one form. Writes and reads needed exactly the same data. There was no performance problem with reading the normalized row directly. The projection would add an async worker, a domain event, a separate read model table, and eventual consistency — for a screen that a user edits once a month. The tech lead’s verdict: this is CRUD. CQRS here is accidental complexity, not structural clarity. The same team had the opposite problem in the order approval workflow: it had no CQRS, it had one normalized Order model used for everything, and the approval dashboard was painfully slow because it joined across seven tables for every row. The right answer is not “always CQRS” or “never CQRS.” It is recognizing which parts of the system have genuine read/write asymmetry and applying the pattern only there.
The real cost of CQRS
CQRS is not free. Before applying it, a team should understand exactly what it introduces:
Two models to design, build, and maintain. Every write-side change must be propagated to one or more read models. Each new read model is a new projection to write, test, and operate. Each change to the write-side schema must be evaluated for its effect on every projection.
Eventual consistency where there was none. An async projection means the read model can be stale. This is a correctness concern that must be explicitly communicated, managed, and designed around. Users see data that is not the latest. Some queries that used to be strongly consistent are now eventually consistent.
Projection infrastructure. Async projection requires a mechanism for observing write-side changes: a domain event bus, change-data capture, or an outbox pattern. Each of these has operational overhead — deployments, monitoring, failure handling, dead-letter queues.
More complex debugging. When a user asks “why does my dashboard show the wrong status,” the answer may involve the write model, the projection pipeline, the read model, and the eventual consistency window. The causal chain is longer and harder to trace.
These costs are worth paying when the structural benefit is real. They are waste when applied to problems that do not have genuine read/write asymmetry.
Most CRUD does not need CQRS
A large proportion of application screens are symmetric: the same fields written to the database are the same fields displayed back. An address form writes a name, street, city, and postal code. The edit screen reads those same four fields. The list screen shows those same fields with a filter. No invariant guards the address fields. No complex join is needed. No read/write structural incompatibility exists.
CQRS for this screen adds two models, a projection, and eventual consistency for zero structural benefit. The write model would be CompanyAddress. The read model would be CompanyAddressView. The projection would copy the four fields from the write table to the read table. This is not architecture — it is ceremony.
The heuristic: if the data a screen writes and the data it reads have the same shape and the same fields, CQRS adds complexity without structural benefit.
For the billing platform:
- Company profile edit (name, address, billing contact) — CRUD, no CQRS
- User account settings (email, timezone, notification preferences) — CRUD, no CQRS
- Order approval dashboard (seven-table join, status aggregation, overdue flags) — genuine asymmetry, CQRS justified
- Order submission (credit-limit invariant, line-item invariants, status transitions) — complex write invariants, CQRS justified
▸Why this works
The question to ask before applying CQRS is not “could we split this into commands and queries?” The answer to that is always yes — any domain can be split. The question is: “does the write concern and the read concern have structurally incompatible requirements that justify maintaining two separate models?” If the answer is yes, CQRS resolves a real structural tension. If the answer is no, CQRS is architectural gold-plating.
Partial and local CQRS
CQRS does not have to be applied to an entire application. The right scope is the bounded context or the domain area where read/write asymmetry actually exists.
In the billing platform, the order management domain has genuine asymmetry: the write side has complex invariants (credit limit, approval workflow, line-item validation), and the read side has complex queries (dashboard, manager pipeline, finance report). CQRS is justified there.
The account management domain is mostly CRUD. Applying CQRS to account management to be “consistent with the order domain” would be cargo-cult architecture — copying the pattern without copying the forces that justify it.
The right answer: apply CQRS locally to the order management domain. Let the account management domain use a simple normalized model with direct reads. The two domains do not need to be architecturally consistent with each other — they need to be appropriate for their own forces.
This is called local CQRS or partial CQRS: CQRS applied selectively where the structural benefit is present, not applied uniformly across the whole system.
CQRS is not event sourcing
The most common misunderstanding about CQRS is conflating it with event sourcing. They are independent patterns that happen to complement each other well. Conflating them doubles the adoption cost.
CQRS splits the write model from the read model. The write side stores current state in a normalized relational table. The read side stores denormalized views. The projection reads the write-side table directly or observes domain events.
Event sourcing (covered in unit 08) stores state as an append-only sequence of events rather than current state. The current state is computed by replaying the event log. Event sourcing is a different write-side storage strategy, independent of how reads are served.
You can have CQRS without event sourcing: the write side is a normalized relational table, the read side is a denormalized projection of that table. This is the most common form of CQRS in production.
You can have event sourcing without CQRS: a service uses an event log as its write store, but serves reads directly from the event log by replaying on demand — no separate read models.
You can have both: the write side is an event log; read models are built by projecting the event log. This combination is powerful but carries the cost of both patterns.
▸lesson.inset.note
Teams that learn about CQRS often encounter it in the context of event sourcing and assume the two always go together. They then decide they cannot adopt CQRS without also adopting event sourcing, which raises the adoption cost to the point where neither gets adopted. The reality: CQRS with a relational write model and a projection that reads the write table is a completely valid, low-ceremony starting point. Event sourcing is an optional — and significant — additional commitment.
A team is building a system with three domains: Order (complex approval workflow, credit-limit invariants, multi-table dashboard), Product Catalog (name, description, price, image — simple CRUD, displayed exactly as stored), and Notifications (user preferences for email/SMS, toggled on a settings page). Which domains should use CQRS?
A team wants to adopt CQRS for their order domain. An engineer says: 'We cannot adopt CQRS without also adopting event sourcing — they go together.' Is this correct?
Six months after applying CQRS to the order domain, the team finds that a new engineer spent a week debugging a bug where the order dashboard showed wrong status. The root cause: the projection had a bug and the read model had stale data for some orders. The team lead asks: 'Was CQRS the wrong choice here?' How should they evaluate this?
- 01What are the main costs CQRS introduces, and when do they pay off?
- 02What is partial/local CQRS and why is it preferred over applying CQRS uniformly?
- 03Are CQRS and event sourcing the same pattern? Can you have one without the other?
The opening story had the right instinct — the tech lead asked the right questions: what invariant does the write model guard, and what asymmetry justifies the projection? For the company address edit screen, the answers were “none” and “none.” CQRS there was ceremony, not architecture.
The costs of CQRS are real: two models, eventual consistency, projection infrastructure, and longer debugging chains. Those costs buy real structural benefits — a clean write model that guards invariants, a read model shaped exactly for each query, independent evolvability of the two sides. The trade-off is justified where the asymmetry is genuine.
Apply CQRS locally to the domain areas where it is warranted. Let other domains stay on simple normalized models. Architectural patterns should follow structural forces, not be applied for consistency’s sake.
CQRS and event sourcing are independent. You can apply CQRS with a relational write model today, without committing to an event log. Event sourcing (unit 08) is a separate, significant architectural decision about write-side storage strategy — complementary to CQRS but not required by it.
Practice
Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.
Something unclear?
Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.