What is CQRS?
Your e-commerce platform is struggling. Writes to the order system need strong consistency and complex validation. Reads for the product catalog need to be blazing fast with denormalized data for search. You're trying to serve both needs with the same data model, and it's not working.
CQRS solves this by separating reads from writes entirely.The Core Idea
Command Query Responsibility Segregation means exactly what it says:COMMANDS (Writes)
- Change system state
- CreateOrder, UpdateProfile
- Can fail validation
- Return success/failure only
- Optimized for consistency
QUERIES (Reads)
- Return data, no side effects
- GetOrderDetails, SearchProducts
- Never modify state
- Return rich view models
- Optimized for performance
Traditional vs CQRS Architecture
Traditional: Single Model
┌─────────────────────────────────────────┐
│ Application │
│ ┌─────────────────────────────────┐ │
│ │ Single Model │ │
│ │ (handles both R & W) │ │
│ └─────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────┐ │
│ │ Single Database │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘
CQRS: Separated Models
┌─────────────────────────────────────────────────────┐
│ Application │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Write Model │ │ Read Model │ │
│ │ (Commands) │ │ (Queries) │ │
│ └────────┬─────────┘ └────────┬─────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Write Store │───▶│ Read Store │ │
│ │ (Normalized) │sync│ (Denormalized) │ │
│ └──────────────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────────┘
Why Separate Reads and Writes?
Reads often 100x more than writes - scale independently
Writes: normalized, validated. Reads: denormalized, fast
Easier to audit and secure write paths separately
Complex domains have complex write logic but simple reads
A Simple Example: E-Commerce Order
Without CQRS (Traditional)
// One model tries to do everything
class Order {
id: string;
customerId: string;
items: OrderItem[];
status: OrderStatus;
shippingAddress: Address;
billingAddress: Address;
// ... 20 more fields for various use cases
// Write logic mixed with read concerns
addItem(item: OrderItem) { /* validation, business rules */ }
// Complex joins needed for display
getOrderSummary() { /* joins with products, customer, shipping */ }
}
With CQRS
// WRITE SIDE: Focus on business rules and validation
class PlaceOrderCommand {
customerId: string;
items: { productId: string; quantity: number }[];
shippingAddressId: string;
}
class OrderCommandHandler {
handle(cmd: PlaceOrderCommand) {
// Validate inventory
// Check customer credit
// Apply business rules
// Emit OrderPlaced event
}
}
// READ SIDE: Optimized for display
interface OrderSummaryReadModel {
orderId: string;
customerName: string; // Denormalized
totalAmount: number; // Pre-calculated
itemCount: number; // Pre-calculated
statusDisplay: string; // Human-readable
estimatedDelivery: Date; // Pre-computed
}
class OrderQueryService {
getOrderSummary(orderId: string): OrderSummaryReadModel {
// Simple, fast lookup from read-optimized store
return this.readDb.findById(orderId);
}
}
Real-World: LinkedIn's Activity Feed
LinkedIn uses CQRS for their activity feed. When you post an update, it goes through complex processing (spam detection, relevance scoring, compliance checks). But when you view your feed, it's a simple read from pre-computed, personalized data stores optimized for fast retrieval.
Writes: Complex validation pipeline | Reads: Sub-100ms feed retrieval
When to Use CQRS
| Use CQRS When | Avoid CQRS When |
|---|---|
| Read/write scaling needs differ significantly | Simple CRUD with equal read/write patterns |
| Complex business logic on writes | Straightforward validation rules |
| Read model differs greatly from write model | One model fits all use cases |
| Team can handle eventual consistency | Strong consistency is mandatory everywhere |
The Trade-offs
CQRS introduces eventual consistency between read and write stores. After a command succeeds, reads might not immediately reflect the change. Your UI and business processes need to handle this gracefully.
- Independent scaling of reads vs writes
- Optimized data models for each purpose
- Cleaner separation of concerns
- Better performance for read-heavy workloads
- Eventual consistency complexity
- More infrastructure to manage
- Data synchronization logic
- Steeper learning curve
Key Takeaways
- CQRS separates reads from writes - Different models, different stores, different scaling
- Commands change state - They carry intent, go through validation, and may fail
- Queries return data - Fast, denormalized, no side effects
- Not always needed - Use CQRS when complexity justifies the overhead
Next up: Event Sourcing Basics - Learning how events become your single source of truth.