The Restaurant Analogy
In a restaurant:
| System | Purpose | Optimized For |
|---|---|---|
| Kitchen (Write) | Takes orders, cooks food | Complex operations, cooking |
| Menu (Read) | Shows dishes, displays prices | Simple lookup, browsing |
They're separate systems serving different needs!
CQRS separates reads from writes for different optimization.
What Is CQRS?
CQRS = Command Query Responsibility Segregation
| Approach | Model |
|---|---|
| Traditional | One model for reading AND writing |
| CQRS | Separate models, each optimized for its purpose |
| Model | Handles | Examples |
|---|---|---|
| Command model | Writes | Create, update, delete |
| Query model | Reads | Get, list, search |
Why Separate Reads and Writes?
Different Requirements
| Writes (Commands) | Reads (Queries) |
|---|---|
| Validate business rules | Just get data fast |
| Ensure consistency | May need joins, aggregations |
| Handle complex logic | Different shapes than writes |
| Need transactions | Can be eventually consistent |
One Model Can't Optimize Both
| Traditional Model | Problem |
|---|---|
| Normalized for writes | Slow for complex queries (JOINs) |
| Denormalized for reads | Complex and slow for writes |
CQRS: Optimize each separately!
How It Works
Simple CQRS (Same Database)
┌─────────────┐
│ Client │
└──────┬──────┘
│
┌────────┴────────┐
↓ ↓
┌─────────┐ ┌─────────┐
│ Command │ │ Query │
│ Handler │ │ Handler │
└────┬────┘ └────┬────┘
└────────┬───────┘
↓
┌──────────┐
│ Database │
└──────────┘
Same database, different code paths.
Full CQRS (Separate Databases)
┌─────────────┐
│ Client │
└──────┬──────┘
│
┌────────┴────────┐
↓ ↓
┌─────────┐ ┌─────────┐
│ Command │ │ Query │
│ Handler │ │ Handler │
└────┬────┘ └────┬────┘
↓ ↓
┌─────────┐ sync ┌─────────┐
│ Write DB│─────→│ Read DB │
└─────────┘ └─────────┘
Different databases, optimized for each purpose.
Commands and Queries
Commands
| Aspect | Description |
|---|---|
| Purpose | Intent to change something |
| Naming | Verbs: CreateOrder, CancelOrder, UpdateProfile |
| Returns | Success/failure, maybe ID |
| Side effects | Yes - modifies state |
Queries
| Aspect | Description |
|---|---|
| Purpose | Request for data |
| Naming | Questions: GetOrder, ListOrders, FindByEmail |
| Returns | Data |
| Side effects | None - pure read |
Benefits
1. Independent Scaling
| Pattern | Scaling |
|---|---|
| 100 reads for 1 write | Scale read servers ×10 |
| Reads don't compete with writes | Better performance |
2. Optimized Models
| Write Model | Read Model |
|---|---|
| Normalized | Denormalized |
| Strong consistency | Pre-computed aggregates |
| Business rules enforced | Fast queries |
3. Different Storage
| Write Side | Read Side |
|---|---|
| PostgreSQL (relational, ACID) | Elasticsearch (search-optimized) |
| Event store | Redis (cache) |
| Transactional | Eventually consistent |
Best tool for each job!
CQRS + Event Sourcing
Common pairing:
| Step | What Happens |
|---|---|
| 1 | Command arrives |
| 2 | Generates events (event sourcing) |
| 3 | Events stored |
| 4 | Events projected to read model |
Benefits:
- Complete audit trail
- Multiple read models from same events
- Time-travel debugging
Eventual Consistency
| Aspect | Description |
|---|---|
| What happens | Write occurs, read model updated asynchronously |
| Delay | Milliseconds to seconds |
| Trade-off | Fast writes, slightly stale reads |
Handling in UI
| Strategy | Implementation |
|---|---|
| Use returned data | After write, use response directly |
| Optimistic update | Show change immediately in UI |
| Processing message | "Your changes are being saved..." |
| Read your writes | Route user's reads to primary |
When to Use CQRS
Good Fit
| Scenario | Why CQRS Helps |
|---|---|
| Read-heavy applications | Scale reads independently |
| Complex queries | Optimize read model for queries |
| Different read/write requirements | Different models for each |
| Event sourcing | Natural pairing |
| Multiple read views | Different projections |
Probably Overkill
| Scenario | Why Not |
|---|---|
| Simple CRUD | Too much complexity |
| Read and write are similar | No benefit |
| Small scale | Overhead not worth it |
| Strong consistency everywhere | Eventual consistency won't work |
Common Mistakes
1. Applying CQRS Everywhere
Not every entity needs CQRS. Apply where it adds value, keep simple parts simple.
2. Ignoring Consistency Needs
Some use cases need immediate consistency (user's own profile, account balance). Use "read your own writes" pattern.
3. Complex Sync Logic
Write DB → Read DB sync can be complex. Events and projections help, but it's more moving parts.
FAQ
Q: Do I need two databases?
No! Start with same database, separate code paths. Add separate storage later if needed.
Q: How are read models updated?
Events from commands → Project to read model. Or direct sync after write.
Q: What about transactions?
Write side has transactions. Eventual consistency to read side.
Q: CQRS without event sourcing?
Yes! CQRS is just separating reads/writes. Event sourcing is a separate pattern.
Summary
CQRS separates read and write operations into different models, optimizing each for its purpose.
Key Takeaways:
- Commands: write operations (verbs)
- Queries: read operations (questions)
- Separate models, different optimization
- Enables independent scaling
- Often paired with event sourcing
- Eventual consistency is a trade-off
- Not for every application
CQRS: Different models for different jobs!
Related Concepts
Leave a Comment
Comments (0)
Be the first to comment on this concept.
Comments are approved automatically.