Command Palette

Search for a command to run...

Level 3 · 30 min

CQRS

CQRS (Command Query Responsibility Segregation) separates the write model (commands that change state) from the read model (queries that return data). This separation enables read models optimized for specific query patterns without polluting the write model.

Commands vs Queries

CQS (Bertrand Meyer): a method should either change state (command) or return data (query), never both. CQRS applies this at the architectural level. Commands: imperative actions that change system state — PlaceOrder, CancelOrder, DepositMoney. Commands are validated, authorized, and executed via domain logic. They may or may not return a value (typically just success/failure or the created ID). They do not return domain state. Queries: requests for data that do not change system state — GetOrderHistory, GetAccountBalance. Queries bypass domain logic and hit the read model directly. Read models are denormalized, query-optimized views that are updated asynchronously. Key insight: the read model and write model can use different storage engines — writes go to a normalized PostgreSQL database, reads go to a Redis cache or Elasticsearch index.

Read Model Projections

A projection is an asynchronously maintained, query-optimized view of domain data. When a command creates or updates data, domain events are published. Projection handlers listen to these events and update the read model. Example: OrderPlaced event → projection handler inserts into a denormalized orders_summary table with all data a dashboard needs (customer name, total, items) — no JOINs required at read time. Projection rebuilding: if the read model becomes corrupted or the projection logic changes, replay all events from the beginning to rebuild the projection. Projections can be specialized: one for the dashboard, one for search, one for analytics — each optimized for its consumers. The write model doesn't change; only read model projections change. The architectural insight behind CQRS: commands express intent ('PlaceOrder'), events record facts ('OrderPlaced'), and read models are purpose-built query artifacts. Facebook's TAO (The Associations and Objects) cache is a real-world CQRS read model over the social graph — writes go to MySQL, reads come from an in-memory cache layer that is eventually consistent. The system handles billions of reads per day precisely because read and write paths are independently optimized and scaled, with the read model shaped for graph traversal rather than relational normalization.

Eventual Consistency and When NOT to Use CQRS

CQRS introduces eventual consistency: between a command completing and the read model being updated, there is a window where the read model is stale. For most web applications, this is acceptable (100ms to seconds). For operations where the user must immediately see their own changes (showing the placed order after checkout), use: a direct read from the write model for that one case, or include the just-created data in the command response. CQRS is NOT for everything. It adds: projection infrastructure, eventual consistency complexity, event schema versioning. Use CQRS when: read and write traffic patterns are drastically different (1000:1 reads to writes), query complexity exceeds what a single normalized model can serve efficiently, read models need to span multiple bounded contexts.

Key Takeaways

  • Commands change state, never return domain data. Queries return data, never change state. This separation enables independent scaling and optimization of read and write paths.
  • Read model projections are asynchronously maintained denormalized views. They can be specialized for different consumers and rebuilt from events at any time.
  • Eventual consistency is the price of CQRS. Design UX to handle it: optimistic updates, loading states, and 'read your own writes' patterns for the cases that cannot tolerate staleness.

Code example

arch005.learn.codeExample