One model that both writes and reads is two jobs wearing one coat. CQRS takes the coat off.
CQRS splits the single data model that normally serves both writes and reads into two independent models: a command model that changes state and returns nothing, and a query model that returns data and changes nothing.
The insight is that these two jobs have opposite requirements. Writes need invariants, validation, transactions, and a normalized shape. Reads need speed, denormalization, and exactly the shape the screen wants. Forcing both through one model means every design decision is a compromise neither side asked for.
Accepts intent — PlaceOrder, CancelBooking. A handler loads the aggregate, enforces business rules, and persists. Returns success or failure, never a payload for display.
Thin handlers over denormalized read models or materialized views. No business logic, no aggregates — just fetch the pre-shaped DTO the caller needs and return it.
The bridge. Changes on the write side propagate to read models via events, a change feed, or replication. This is where eventual consistency enters the system.
Because the paths no longer share a model, each can scale, cache, and be stored differently — often a relational write store beside document or search read stores.
Checkout enforces stock and pricing invariants on the write side, while product pages and order history are served from denormalized read models that survive traffic spikes.
Transfers are commands guarded by strict balance invariants; statements, running balances, and spend analytics are projections rebuilt from the ledger.
Issue trackers and docs apps accept small writes (assign, comment, edit) but serve heavy filtered boards and search from separate indexes such as Elasticsearch.
Operational writes land in a transactional store; dashboards read pre-aggregated tables so no report query ever competes with a customer's checkout.
A service owns its write model and publishes events; other services build their own local read models instead of querying across service boundaries.
A command changes state and returns no data; a query returns data and changes nothing. What concretely breaks when a single endpoint does both — and why does that make caching and retrying harder?
Hint: think about what a client can safely retry, and what a proxy can safely cache.Your read model lags the write model by 200 ms. Name one user-visible symptom, and one way to hide it without making the read path synchronous.
Hint: the user who just wrote is the one most likely to notice. What could you show them instead of a fresh query?CQRS and Event Sourcing are almost always mentioned together. Which one can you adopt without the other, and what specifically do you give up by adopting only that one?
Hint: one is about separating models; the other is about how state is stored. Only one implies the other.