CQRS

CQRS

Part 7: CQRS - Command Query Responsibility Segregation - Complete Guide

Part 7: CQRS (Command Query Responsibility Segregation)

Separating Reads from Writes for Scalability and Performance - Complete Guide

📌 Quick Summary (30 seconds):
CQRS separates write operations (Commands) from read operations (Queries) into different models. Each side has its own optimized data structure, database, and scaling strategy. The write side is normalized for consistency; the read side is denormalized for fast queries. Data sync happens asynchronously via events. This intentionally duplicates data — but that's the trade-off for scalability.

❓ The Problem: One Model Doesn't Fit All

Traditional CRUD Problems:
  • Same model used for both reads and writes (compromised design)
  • Complex JOINs for read operations (slow performance)
  • Lock contention between reads and writes
  • Cannot scale reads and writes independently
  • Read models often require different structure than write models

🔄 What is CQRS?

CQRS Architecture Flow

┌─────────────────────────────────────────────────────────────────────┐
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ COMMAND │ │ QUERY │ │
│ │ (Write) │ │ (Read) │ │
│ └──────┬───────┘ └──────▲───────┘ │
│ │ │ │
│ ▼ │ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ OrderWrite │ │ OrderRead │ │
│ │ Service │ │ Service │ │
│ └──────┬───────┘ └──────▲───────┘ │
│ │ │ │
│ ▼ │ │
│ ┌──────────────┐ Event ┌──────────────┐ │
│ │ OrderWrite │ ───────────────► │ OrderRead │ │
│ │ Database │ (async) │ Database │ │
│ │ (Normalized) │ │ (Denormalized)│ │
│ └──────────────┘ └──────────────┘ │
│ │
│ Write Model: 3NF, normalized, strong consistency │
│ Read Model: Denormalized, cached, eventual consistency │
└─────────────────────────────────────────────────────────────────────┘

📊 Command vs Query - The Separation

✏️ COMMAND (Write)
  • Changes system state
  • Returns no data (or just confirmation)
  • Examples: Create, Update, Delete
  • Validates business rules
  • Strong consistency required
  • Normalized data structure
  • Scale differently
🔍 QUERY (Read)
  • Does NOT change state
  • Returns data
  • Examples: Get, List, Search
  • No business validation
  • Eventual consistency acceptable
  • Denormalized data structure
  • Can scale independently

🎯 Core Principles of CQRS

PrincipleWrite Model (Command)Read Model (Query)
Data Structure Normalized (3NF, no duplication) Denormalized (with intentional duplication)
Optimization For Consistency, Validation, Business Logic Performance, Fast Retrieval, Filtering
Database Type Relational (PostgreSQL, Oracle) NoSQL / Search (Elasticsearch, MongoDB)
Scaling Strategy Vertical / Limited horizontal Horizontal (many replicas)
Consistency Strong (ACID) Eventual (BASE)
Caching Minimal or none Aggressive caching

📝 Intentional Data Duplication

⚠️ This is NOT a mistake - it's by design!
In CQRS, data duplication is intentional. The write model is the source of truth. The read models are projections optimized for specific queries.
Example: Order Data Duplication

Write Model (Normalized - Single Source of Truth)
┌─────────────────────────────────────────────────────────────┐
│ orders table: │
│ order_id (PK) | customer_id | order_date | status │
│ 1001 | CUST123 | 2024-01-15 | PAID │
│ │
│ order_items table: │
│ item_id (PK) | order_id | product_id | quantity | price │
│ 1 | 1001 | PROD456 | 2 | 25.00 │
│ 2 | 1001 | PROD789 | 1 | 50.00 │
└─────────────────────────────────────────────────────────────┘

│ Event (OrderCreated / OrderUpdated)

Read Model (Denormalized - Multiple Projections)
┌─────────────────────────────────────────────────────────────┐
│ order_summary_view (for listing orders): │
│ order_id | customer_name | total_amount | status │
│ 1001 | John Doe | 100.00 | PAID │
│ │
│ order_detail_view (for order details page): │
│ order_id | customer_name | items[] | total | status │
│ 1001 | John Doe | [...2 items] | 100.00 | PAID │
│ │
│ customer_order_history (for customer dashboard): │
│ customer_id | order_count | total_spent | last_order_date │
│ CUST123 | 15 | 2500.00 | 2024-01-15 │
└─────────────────────────────────────────────────────────────┘

⚡ Pros & Cons of CQRS

✅ ADVANTAGES
  • Read and Write scale independently
  • Optimized schemas for each use case
  • No lock contention between reads and writes
  • Can use different databases (polyglot persistence)
  • Better performance for complex queries
  • Read models can be highly denormalized and cached
  • Security: separate permission models
❌ DISADVANTAGES / CHALLENGES
  • Increased complexity (two models instead of one)
  • Eventual consistency (read may be stale)
  • Data duplication (storage cost)
  • Harder to keep read models in sync
  • More moving parts to manage
  • Overkill for simple CRUD applications
  • Eventual consistency confuses some teams

🎯 When to Use CQRS (And When NOT to)

✅ USE CQRS When:
  • Read and write have different performance requirements
  • Complex queries with many JOINs are slowing down writes
  • High read-to-write ratio (e.g., 1000:1)
  • Multiple different read models needed (mobile, web, API)
  • Event Sourcing is already being used
  • Team has experience with eventual consistency
  • Domain is complex with different read/write models naturally
❌ DON'T USE CQRS When:
  • Simple CRUD application
  • Same model works fine for both reads and writes
  • Team is new to microservices / distributed systems
  • Strong consistency is required for all reads
  • Low traffic volume (unnecessary complexity)
  • Startup / MVP phase (start simple, add later)
  • No clear separation between read and write concerns

🔗 CQRS + Event Sourcing (Perfect Together)

CQRS with Event Sourcing

┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Command │ │ Event │ │ Event │
│ Handler │────►│ Store │────►│ Projector │
└─────────────┘ │ (Kafka/ │ └──────┬──────┘
│ EventStore)│ │
└─────────────┘ ▼
┌─────────────┐
│ Read │
│ Model │
│ (Database) │
└─────────────┘
Benefits of combining CQRS + Event Sourcing:
• Event Store = Source of Truth (immutable audit log)
• Read Models can be rebuilt by replaying events
• Temporal queries (what state at time X?)
• Complete audit trail of every change

✈️ Real-World Examples

IndustryExampleWrite ModelRead Model(s)
E-Commerce Order Management Normalized orders, items, payments Customer order history, Admin dashboard, Analytics reports
Banking Transaction Processing Ledger entries (immutable) Balance view, Transaction history, Fraud detection view
Social Media News Feed Posts, follows, likes (write-optimized) Personalized feed, Trending posts, User profile view
Logistics Package Tracking Status updates (immutable timeline) Customer tracking view, Internal dashboard, Analytics

🔄 Keeping Read Models in Sync

PatternHow It WorksBest For
Event-based (Most Common) Write side publishes events → Read side subscribes & updates Most use cases, loosely coupled
Change Data Capture (CDC) Capture DB changes from write model → Stream to read model When you can't change write side code
Batch Sync Periodic job updates read models (hourly/daily) When eventual consistency can be minutes/hours
API-based Read side calls write API to refresh when needed Simpler but more coupling

📋 Command vs Query - Complete Comparison

Command Flow (Write)
Client → POST /orders → Validate → Apply Business Rules → Save to Write DB → Publish Event → Return Order ID

Query Flow (Read)
Client → GET /orders/123 → Check Cache → Query Read DB → Return Denormalized Data

Sync Flow (Event Handler)
Event Received → Transform Data → Update Read DB → Invalidate Cache (optional)

💡 Interview Power Phrases

"CQRS intentionally duplicates data — that's the trade-off for scalability and performance. Write-optimized and read-optimized models can scale independently."
"We use CQRS when read and write have different performance characteristics. For example, complex reporting queries shouldn't slow down order placement."
"CQRS pairs perfectly with Event Sourcing. The event store becomes the source of truth, and we can rebuild any read model by replaying events."
"The read side is eventually consistent. We accept that a user might not see their new order for a few milliseconds — that's acceptable for our use case."

📚 Quick Memory Reference

🔑 Key Terms
  • Command - Write operation (changes state)
  • Query - Read operation (no state change)
  • Write Model - Normalized, consistent
  • Read Model - Denormalized, fast queries
  • Projection - Read model built from events
  • Eventual Consistency - Reads may be slightly behind
🧠 Mental Models
  • Write Model = Official ledger
  • Read Model = Display copy / cache
  • Commands = Actions that change state
  • Queries = Questions about state
  • Event Sourcing = Replayable tape recording
📌 One-Line Summary

"CQRS separates writes from reads into different models — trade consistency for scalability, duplicate data for performance."

📊 Degrees of CQRS (It's a Spectrum)

LevelDescriptionWhen to Use
Level 0: Traditional CRUD Single model for both reads and writes Simple apps, MVP, small teams
Level 1: Logical Separation Same database, different services/controllers Basic optimization, same team
Level 2: Separate Services Different services, same database Different scaling needs, different teams
Level 3: Separate Databases Different services, different databases, sync via events Full CQRS, complex domain, high scale

✅ Key Takeaways (Remember These 5 Points)

  • Separation: Commands (writes) and Queries (reads) are completely separate
  • Data Duplication: Intentional and by design - read models are projections
  • Eventual Consistency: Reads may be stale; acceptable for many use cases
  • Independent Scaling: Scale read side separately from write side
  • Not for CRUD: Only use when complexity is justified by benefits
🎯 Remember This:
Traditional CRUD = One model does everything (compromised)
CQRS = Two models, each optimized for its purpose (scalable)
CQRS + Event Sourcing = Immutable audit log + rebuildable read models