Design & Architecture

CQRS — Command Query Responsibility Segregation

Advanced

CQRS splits the model you use to change state (the write side, driven by commands) from the model you use to read state (the read side, optimised for queries). They can have different shapes, different storage, even different scaling. It is the architectural cousin of CQS, and it pays off when reads and writes have genuinely different needs — but it adds moving parts, so apply it where that tension is real.

In a single shared model, the same entities serve both "change this" and "show me this", and you end up compromising both: the write model is cluttered with display concerns, and queries are awkward joins over a normalised schema. CQRS accepts that these are different jobs and gives each its own model. Commands validate business rules and produce state changes; queries return purpose-built read models (often denormalised) and never change anything.

CQRS is frequently confused with CQS (a method-level principle) and with Event Sourcing (a storage approach). They compose well but are independent: you can do CQRS without event sourcing, and CQS everywhere without either. This page is about the system-level split; see also Event-Driven Architecture and Distributed Systems & Consistency.

Separate the two responsibilities

One model strained both ways // Same EF entity used to enforce rules AND render a dashboard:
var c = db.Customers.Include(x => x.Cases).Include(x => x.Documents)
.Include(x => x.Screenings).First(x => x.Id == id);
c.RiskLevel = Recompute(c); // a write, mid-query, on a giant graph

The model is overfetched for display yet also mutated for rules. Both jobs are done badly and the write is hidden inside a read.

Command and query split // write side: small, invariant-protecting
await handler.Handle(new RecomputeRisk(customerId));

// read side: flat, fast, read-only DTO
CustomerDashboardDto dto = await queries.GetDashboard(customerId);

Changing state and showing state are separate paths with separate models. Each is simple, and the write is explicit rather than smuggled into a read.

Adopt it where the tension is real

Mind the consistency gap

Self-review checklist

Why it matters: Reads and writes pull a shared model in opposite directions — writes want a small model that protects invariants, reads want a wide model that's fast to display. CQRS resolves that by giving each its own model, which keeps the write side trustworthy and the read side quick. The cost is more moving parts and, when stores are separate, eventual consistency to manage — so it earns its place where the read/write tension is real and is overkill where it is not.