Day 022: CQRS - Separating Reads from Writes
Topic: CQRS pattern fundamentals
π‘ Today's "Aha!" Moment
The insight: Don't use the same model for writes and reads. Commands change state, queries read stateβthey have different needs. CQRS separates them to optimize each independently.
Why this matters:
Traditional architecture uses one model for everything:
User β API β Single Model β Database
β
(Handles both writes AND reads)
This creates conflicts:
- Writes need: Validation, business rules, consistency, transactions
- Reads need: Speed, denormalization, caching, different views
Same model can't optimize for both. Write-optimized schema (normalized, third normal form) makes reads slow (JOINs everywhere). Read-optimized schema (denormalized) makes writes complex (update in multiple places).
CQRS splits the model:
Commands (Write Side):
User β CommandHandler β Write Model β Event Store
β
(Publishes events)
β
Queries (Read Side): Event Bus
User β QueryHandler β Read Model β (Subscribes to events)
Write side: Focus on business logic, validation, consistency
π Why This Matters
CQRS enables independent scaling and optimization of read and write workloads. Companies like Amazon, Netflix, and LinkedIn use this pattern to handle millions of operations while maintaining flexibility and performance.
π― Daily Objective
Master Command Query Responsibility Segregation (CQRS): understand how separating write models from read models enables scalability, flexibility, and optimized performance. Build a CQRS system that demonstrates the pattern's power.
π Topics Covered
Read side: Focus on speed, denormalization, specific views
Sync via events: Changes flow from write β read asynchronously
The pattern: Separate models for commands (writes) and queries (reads)
This pattern appears wherever read and write needs differ:
- Databases: OLTP (writes) vs OLAP (reads/analytics) use different schemas
- CDN caching: Origin server (writes) vs edge cache (reads)
- Git: Write to main branch, read from clones/mirrors
- Microservices: Service owns writes, publishes to read-only replicas
- YouTube: Upload service (writes) vs streaming/search (reads)
How to recognize CQRS fits:
| Use CQRS when: | Skip CQRS when: |
|---|---|
| Reads far exceed writes (100:1+) | Equal read/write ratio |
| Need multiple read views from same data | Single simple view suffices |
| Complex business logic on writes | Simple CRUD operations |
| Read performance critical (search, dashboards) | Performance not critical |
| Event-driven architecture already in place | Monolithic synchronous system |
Common misconceptions:
β Myth: "CQRS requires Event Sourcing"
β
Truth: CQRS and Event Sourcing are independent. Can use CQRS with traditional database (write model updates DB, triggers sync to read model).
β Myth: "CQRS means eventual consistency always"
β
Truth: Can be synchronous (write model updates read model in same transaction). Usually async for scale, but not required.
β Myth: "CQRS is all-or-nothing"
β
Truth: Apply selectivelyβonly for bounded contexts where it adds value. Rest of system can be traditional.
β Myth: "Two databases = CQRS"
β
Truth: CQRS is about two models, not two databases. Can have both models in same DB (different schemas).
Real-world examples:
-
Netflix:
-
Write side: User rates movie, updates profile β Cassandra (consistent writes)
- Read side: Homepage recommendations β ElasticSearch (fast queries)
-
Events: RatingChanged, ProfileUpdated β sync read models
-
Amazon product catalog:
-
Write side: Seller updates product β RDBMS (transactions, validation)
- Read side:
- Search β ElasticSearch (full-text, facets)
- Recommendations β Graph DB (related products)
- Mobile app β NoSQL (fast denormalized reads)
-
Same data, multiple read models optimized for different queries
-
Uber:
-
Write side: Trip requested β Write DB (availability, pricing, matching)
-
Read side:
- Rider app β "Where's my driver?" (location updates)
- Driver app β "New trip request" (geospatial queries)
- Analytics β Data warehouse (reporting)
-
LinkedIn feed:
-
Write side: User posts update β Master DB (consistency)
- Read side: Feed queries β Cached, denormalized views (billions of reads/day)
-
Events: PostCreated β rebuild affected feeds asynchronously
-
GitHub pull requests:
- Write side: Comment, approve, merge β Git + metadata DB
- Read side: PR timeline view β Denormalized (all comments, reviews, commits together)
What changes after this realization:
You stop forcing one model to serve two masters. Instead of fighting between "normalize for writes" vs "denormalize for reads," do both in separate models.
You recognize eventual consistency is OK for reads:
- User posts comment β Appears instantly for them (synchronous)
- Other users see comment 100ms later β Acceptable (asynchronous)
You understand query models are projections:
// Write Model (normalized)
tables: users, posts, comments, likes
// Read Model (denormalized for feed query)
{
postId: "123",
author: { name, avatar },
content: "...",
commentCount: 42,
likeCount: 156,
topComments: [...] // Pre-computed!
}
Read model optimized for specific query (no JOINs, no counting, instant response).
You learn synchronization strategies:
- Event-driven (most common): Write side publishes events, read side subscribes
- Change Data Capture (CDC): Database triggers/log tailing updates read models
- Dual writes: Update both models (risky, can get out of sync)
- Scheduled sync: Batch updates (acceptable for analytics)
Trade-offs:
- Pros: Independent scaling, optimized performance, multiple views
- Cons: Complexity, eventual consistency, sync overhead, more code
Meta-insight:
CQRS acknowledges that reading and writing are fundamentally different operations. Trying to use one model for both is like using a hammer for screwsβit works but it's not optimal.
This mirrors real-world specialization:
- Libraries: Librarians shelve books (write), patrons find books (read)βdifferent skills
- Factories: Assembly line (write), quality control (read)βdifferent processes
- Cities: Zoning laws separate industrial (write-heavy) from residential (read-heavy)
The deeper principle: Optimize for actual usage patterns. If 99% of traffic is reads, don't let write constraints slow reads. If writes need consistency, don't let read denormalization complicate writes.
CQRS is permission to specializeβbuild the best write model for writes, best read model for reads, and sync them. Complexity increases, but each piece becomes simpler because it has one job.
The most elegant systems aren't the cleverestβthey're the ones that match structure to use case. CQRS does this by acknowledging that commands and queries are different beasts deserving different treatment.
π Detailed Curriculum (Optimized for 60 min)
-
Video Introduction (12 min) β START HERE
-
Udi Dahan: "CQRS - Clarified"
- Video Link
-
Pattern explanation and common mistakes
-
Core Reading (18 min)
-
Martin Fowler: "CQRS"
- Article
- Microsoft CQRS Journey: Chapter 1
-
Focus: Core pattern, when to use, scalability benefits
-
Quick Synthesis (5 min)
- Draw: Command side vs Query side diagram
- Write: "Why separate reads and writes?"
- Note: 3 benefits and 3 challenges
π Resources
π― Core Resources (Use Today)
-
Video: Udi Dahan - CQRS Clarified (12 min)
Why it matters: Udi coined the CQRS term and clarifies common misconceptions. Essential for avoiding pitfalls. -
Article: Martin Fowler - CQRS
Why it matters: Canonical reference explaining when CQRS is appropriate and when it's overkill. -
Doc: Microsoft CQRS Journey - Introduction
Why it matters: Real-world enterprise patterns and practices from Microsoft's reference implementation. -
Tutorial: Simple CQRS Example with Node.js
Why it matters: Hands-on code showing CQRS implementation without overwhelming complexity.
β Bonus Resources (If Extra Time)
-
Book: CQRS Journey - Microsoft patterns & practices
Why it matters: Comprehensive guide covering implementation challenges and solutions. -
Article: CQRS Facts - Udi Dahan
Why it matters: Explains when NOT to use CQRS - crucial for avoiding over-engineering. -
Video: Greg Young - CQRS and Event Sourcing
Why it matters: Deep dive into how CQRS and Event Sourcing complement each other. -
Case Study: How Uber uses CQRS in Microservices
Why it matters: Production example of CQRS at massive scale handling millions of trips.
βοΈ Practical Activities (25 min total)
1. Implement CQRS System (15 min)
Build a simple blog system with CQRS:
// COMMAND SIDE (Write Model)
// Handles business logic and state changes
class PostCommands {
eventStore
createPost(postId, title, content, authorId) {
// Validate
if (title.isEmpty()) throw "Title required"
// Create event
event = PostCreated {
postId, title, content, authorId,
timestamp: now()
}
// Store event
eventStore.append(event)
// Publish event to read side
eventBus.publish(event)
}
publishPost(postId) {
// Load post from events
post = Post.fromEvents(eventStore.getEvents(postId))
// Validate can publish
if (post.isPublished) throw "Already published"
// Create and store event
event = PostPublished { postId, timestamp: now() }
eventStore.append(event)
eventBus.publish(event)
}
}
// QUERY SIDE (Read Model)
// Optimized for fast reads
class PostQueries {
database // Denormalized read database
// Listen to events and update read models
onEvent(event) {
match event {
PostCreated => {
database.insert({
id: event.postId,
title: event.title,
content: event.content,
author: event.authorId,
status: "draft",
createdAt: event.timestamp
})
}
PostPublished => {
database.update(event.postId, {
status: "published",
publishedAt: event.timestamp
})
}
}
}
// Optimized query methods
getPost(postId) {
return database.find(postId)
}
getRecentPosts(limit) {
return database.query(
where: { status: "published" },
orderBy: "publishedAt DESC",
limit: limit
)
}
getPostsByAuthor(authorId) {
return database.query(
where: { author: authorId },
orderBy: "createdAt DESC"
)
}
}
// EVENT BUS (Communication between sides)
class EventBus {
subscribers = []
subscribe(handler) {
subscribers.push(handler)
}
publish(event) {
for handler in subscribers {
handler.handle(event)
}
}
}
Tasks:
- Implement command side (write operations)
- Implement query side (read operations)
- Connect via event bus
- Demonstrate: create post β query post β publish β query again
- Show that read model is denormalized and optimized
2. Architecture Diagram (7 min)
Draw the complete CQRS architecture:
βββββββββββββββ
β Client β
ββββββββ¬βββββββ
β
ββββββββ΄βββββββ
β β
Commands Queries
β β
v v
βββββββββββββ ββββββββββββ
β Command β β Query β
β Side β β Side β
β (Write) β β (Read) β
βββββββ¬ββββββ βββββββ²βββββ
β β
v β
ββββββββββββ βββββββ΄βββββ
β Event βββββΆβ Event β
β Store β β Handler β
ββββββββββββ ββββββββββββ
β
v
ββββββββββββ
β Read β
β Database β
ββββββββββββ
3. Reflection (3 min)
Answer:
- Why denormalize the read side?
- What is "eventual consistency"?
- When is CQRS overkill?
π¨ Creativity - Quick Mental Reset (5 min)
Quick Exercise: "Command vs Query"
Draw two faces:
- Command face: Serious, focused, changing things
- Query face: Calm, observing, just looking
Label them with examples:
- Commands: CreateUser, UpdatePost, DeleteComment
- Queries: GetUser, ListPosts, SearchComments
Purpose: Internalize the mental model of commands changing state vs queries reading state.
Take 3 min to draw, 2 min to think about how they differ in your own systems
π Connections to Previous Learning
From Yesterday:
- Event Sourcing: CQRS naturally pairs with event sourcing
- Events: Same events drive both write and read models
From Month 1:
- Distributed Systems (M1W1): Commands/queries can be on different servers
- Eventual Consistency (M1W2): Read model updates asynchronously
- CAP Theorem (M1W2): CQRS trades consistency for availability/performance
Building Forward:
- Tomorrow (Sagas): Commands orchestrate distributed workflows
- Day 4 (Streams): Events flow from write to read models
- Week 2 (Tracing): Tracking commands/queries through system
β Daily Deliverables (Must Complete)
- [ ] Watch Udi Dahan's CQRS video
- [ ] Read Martin Fowler's CQRS article
- [ ] Implement command side with 2 commands
- [ ] Implement query side with 2 queries
- [ ] Connect them via event bus
- [ ] Demonstrate eventual consistency
- [ ] Draw complete CQRS architecture diagram
- [ ] Write 3 benefits and 3 challenges of CQRS
β Bonus (If Extra Time)
- [ ] Add multiple read models (list view, detail view, search)
- [ ] Implement read model rebuild from event store
- [ ] Handle command validation and business rules
- [ ] Add optimistic concurrency control
- [ ] Explore projections and materialized views
π― Success Criteria
By the end of today, you should be able to:
- β Explain CQRS pattern and its motivation
- β Implement separate command and query models
- β Connect write and read sides via events
- β Describe eventual consistency trade-offs
- β Identify when CQRS is appropriate
- β Design denormalized read models
β° Total Estimated Time (OPTIMIZED)
- π Core Learning: 35 min (video 12 + reading 18 + synthesis 5)
- π» Practical Activities: 25 min (implementation 15 + diagram 7 + reflection 3)
- π¨ Mental Reset: 5 min (command vs query drawing)
- Total: 60 min (1 hour) β
Note: Focus on the pattern, not perfection. A simple working example that shows separate read/write models is excellent!
π Today's Big Ideas
- Separation of Concerns: Writes optimize for consistency, reads for performance
- Eventual Consistency: Read models update asynchronously
- Scalability: Read and write sides scale independently
- Flexibility: Multiple read models from same write model
- Simplicity: Each side is simpler because it has one job
π‘ CQRS in the Real World
- Netflix: Separate models for streaming vs recommendations
- Amazon: Product write model vs various query models (search, recommendations, reviews)
- Uber: Trip commands vs rider/driver query models
- LinkedIn: Profile updates vs feed queries
π Tomorrow's Preview
Saga Pattern: Distributed Transactions
You'll learn how to coordinate long-running transactions across multiple services without distributed transactions. Sagas use commands and events to maintain consistency in microservices!
"CQRS is not a top-level architecture. It's a pattern that's applicable in specific places." - Martin Fowler
You're mastering production patterns! πͺ