CQRS (Command Query Responsibility Segregation)
CQRS is an architectural pattern that separates the data model used for writes (commands) from the data model used for reads (queries), allowing each to be optimized independently for its specific workload.
What It Really Means
In a traditional CRUD application, the same data model serves both reads and writes. You insert a row into a normalized relational table, and you query that same table for the UI. This works fine for simple applications, but at scale, reads and writes have fundamentally different requirements.
Writes need strong consistency, validation, and complex business logic. Reads need fast response times, denormalized data for the UI, and often serve 10-100x more traffic than writes. Optimizing a single model for both is a compromise — normalize for write integrity and reads require expensive joins, denormalize for read speed and writes become error-prone.
CQRS, formalized by Greg Young around 2010 (building on Bertrand Meyer's Command-Query Separation principle), proposes using two separate models: a write model optimized for accepting and validating commands, and a read model (one or more) optimized for serving queries. The two models are connected through an event or synchronization mechanism.
CQRS is used by financial trading platforms, e-commerce systems with complex product catalogs, social media feeds, and any system where read and write workloads have very different scaling requirements.
How It Works in Practice
The Two Sides
Command Side (Write Model):
- Receives commands:
CreateOrder, UpdateUserProfile, TransferFunds
- Validates business rules and invariants
- Persists state changes to the write database
- Publishes domain events:
OrderCreated, ProfileUpdated, FundsTransferred
- Optimized for consistency and correctness
Query Side (Read Model):
- Receives queries:
GetOrderDetails, SearchProducts, GetUserDashboard
- Reads from a denormalized read database (or materialized views)
- Optimized for speed and the specific query patterns of the UI
- Updated asynchronously by consuming domain events from the command side
Real-World: E-Commerce Product Catalog
Write model (normalized PostgreSQL):
products table with core attributes
product_variants table (sizes, colors)
product_categories join table
pricing_rules table with complex logic
Read models (optimized for specific views):
- Product listing (Elasticsearch): Denormalized documents with name, price, image, category, rating — one document per product for fast search
- Product detail page (Redis): Pre-computed JSON with all variants, reviews summary, related products — served in <10ms
- Admin dashboard (PostgreSQL materialized view): Aggregated sales data, inventory levels
When a product manager updates a price, the command side validates business rules (no price below cost, approval required for >20% changes), persists to PostgreSQL, and publishes PriceUpdated. Event consumers update Elasticsearch and Redis within milliseconds.
Real-World: Financial Trading
A trading platform processes thousands of trades per second (write-heavy) while serving real-time portfolio views to millions of users (read-heavy).
- Write model: Append-only trade log with strict validation (position limits, regulatory checks)
- Read model 1: Real-time position aggregation (in-memory cache)
- Read model 2: Historical trade search (Elasticsearch)
- Read model 3: Regulatory reporting (columnar database)