Implementing CQRS Pattern
1. Understanding CQRS Principles
| Principle | Detail |
|---|---|
| Command | Mutates state; returns ack/ID |
| Query | Reads state; never mutates |
| Separation | Different models, possibly stores |
| When useful | High read/write asymmetry, complex domain |
| When overkill | Simple CRUD apps |
2. Separating Read and Write Models
| Model | Optimized For |
|---|---|
| Write Model | Domain rules, normalization, transactions |
| Read Model | Query shape, denormalized, indexed |
| Sync | Events / CDC update read store |
| Storage | Postgres for write, Elastic/Redis for read |
3. Implementing Command Handlers
| Step | Action |
|---|---|
| Validate | Schema + business invariants |
| Load aggregate | From repo / event stream |
| Invoke domain | Method on aggregate root |
| Persist | Save events / state |
| Publish | Emit domain events |
Example: Command handler (Java)
public record PlaceOrder(String orderId, String customerId, List<Item> items) {}
@Component
class PlaceOrderHandler {
public void handle(PlaceOrder cmd) {
Order order = Order.create(cmd.orderId(), cmd.customerId(), cmd.items());
orderRepo.save(order);
eventBus.publishAll(order.pullEvents());
}
}
4. Implementing Query Handlers
| Aspect | Detail |
|---|---|
| Bypass aggregates | Query read model directly |
| Return DTOs | Shaped per UI need |
| No side effects | Pure function of state |
| Caching | Safe (idempotent) |
5. Synchronizing Read and Write Models
| Mechanism | Detail |
|---|---|
| Domain events | Projection updates read model |
| CDC | Debezium → projector |
| Outbox | Reliable publication |
| Lag | Monitor & alert (eventual consistency) |
6. Implementing Event Sourcing with CQRS
| Layer | Detail |
|---|---|
| Write side | Append events to stream |
| Read side | Projections subscribe to stream → build views |
| Multiple views | Same events power many query models |
| Rebuild | Drop view, replay events |
7. Handling Eventual Consistency
| Tactic | Detail |
|---|---|
| Optimistic UI | Show command result immediately |
| Read-your-writes | Force read from primary or pin user to writer |
| Polling / WS | Refresh until projection catches up |
| Display lag indicator | "Syncing..." badge |
8. Designing Read Model Projections
| Aspect | Detail |
|---|---|
| Per-view projection | One read model per UI screen |
| Idempotent apply | Track applied event IDs |
| Schema evolution | Versioned, replayable |
| Storage | Postgres view, Elastic, Redis hash |
9. Optimizing Query Performance
| Technique | Detail |
|---|---|
| Denormalization | Pre-join into single document |
| Indexes | Per query pattern |
| Materialized views | Refresh on event |
| Cache layer | Redis with event-driven invalidation |
| Search engine | Elastic / OpenSearch for text + facets |
10. Managing CQRS Complexity
| Complexity Source | Mitigation |
|---|---|
| Two models to maintain | Generate read model from events |
| Eventual consistency | Hide via UX |
| Operational overhead | Apply only to bounded contexts that benefit |
| Debugging | Trace events through projections |
11. Implementing Command Validation
| Layer | Validation |
|---|---|
| Syntactic | Schema (JSON Schema, Bean Validation) |
| Semantic | Cross-field, format, ranges |
| Business | Aggregate invariants enforced in domain |
| Auth | Caller permitted to issue command |
12. Using Event-Driven Synchronization
[Command] → [Aggregate] → [Event Store]
│
┌───────────┼───────────┐
▼ ▼ ▼
[Order View] [Search Index] [Analytics]
▲
└── [Query] ◄── Client
| Element | Tool Choice |
|---|---|
| Bus | Kafka, RabbitMQ, NATS JetStream |
| Projector | Stateless consumer per view |
| Checkpoint | Persist last applied event ID |