CQRS Pattern
Separate write and read models to optimize each side independently.
📌 Key IdeaCQRS (Command Query Responsibility Segregation) splits command and query models. Writes enforce domain rules; reads use denormalized projections. It improves scalability and clarity in complex domains but adds eventual consistency and operational overhead.
Problem it solves
- One shared CRUD model often becomes a compromise between write correctness and read performance.
- Complex queries can pollute domain models and slow down transactional paths.
- CQRS separates command and query concerns so each side can be designed and scaled for its own purpose.
Definition
In CQRS, command side handles state-changing operations and invariants; query side serves read-optimized views. Both models can have different data structures and scaling strategies.
Why It Matters
- Read and write workloads often have different access patterns.
- Read models can be optimized without polluting write-domain logic.
- Supports high-scale APIs where query traffic dominates writes.
When to Use
- Complex domains with heavy reads and comparatively fewer writes.
- Need for specialized query models (dashboards/reporting/search views).
- Teams comfortable operating async pipelines and projection lag.
When NOT to Use
- Simple CRUD apps with low scale and straightforward queries.
- Teams not ready for eventual consistency debugging and replay tooling.
Code Variations
1️⃣ Command side sketchjava
public void handle(PlaceOrderCommand cmd) {
Order order = orderRepo.load(cmd.orderId());
order.place(cmd.items()); // domain rules enforced here
orderRepo.save(order);
eventBus.publish(new OrderPlaced(cmd.orderId(), cmd.items()));
}2️⃣ Query projection updatejava
@EventListener
public void on(OrderPlaced e) {
orderReadRepo.upsert(new OrderSummaryView(
e.orderId(), "PLACED", e.items().size()
));
}Read side can use denormalized schema for fast API responses.
3️⃣ Query endpoint (read model only)java
@GetMapping("/orders/{id}")
public OrderSummaryView get(@PathVariable String id) {
return orderReadRepo.findById(id)
.orElseThrow(() -> new NotFoundException("order not found"));
}Traditional CRUD vs CQRS
| Aspect | Traditional CRUD | CQRS |
|---|---|---|
| Model | Single shared model | Separate command/query models |
| Read optimization | Limited by write model shape | Dedicated projections for query speed |
| Consistency | Usually immediate in one DB tx | Often eventual between write and read model |
| Complexity | Lower | Higher (projections, sync, tooling) |
Common Confusions Cleared Up
CQRS is independent of Event Sourcing
You can implement CQRS without event sourcing (using normal DB writes + change events). Event sourcing is a persistence strategy, not a mandatory CQRS requirement.
Eventual consistency is a product decision
If read model lags write model, user may not see latest state instantly. UX and API contracts should communicate pending states.
Common Mistakes / Anti-Patterns
- Introducing CQRS before proving read/write divergence.
- No replay/rebuild strategy for corrupted read projections.
- Using synchronous cross-service calls in every projection update.
Interview Q&A
Quick Cheat Sheet
Command sideValidates and changes domain state.
Query sideServes denormalized read views.
Trade-offPerformance/scalability vs operational complexity.
Design keyPlan for projection lag and rebuilds.