Implementing CQRS Patterns

1. Command Model Pattern

AspectDetail
PurposeValidates business rules and applies state changes
SchemaNormalized, optimized for invariants
ReturnsAcknowledgment / event, not data
ExamplesPlaceOrder, CancelOrder, ApplyDiscount

Example: Command + Handler

public record PlaceOrderCommand(CustomerId customer, List<Item> items) {}

@Component
public class PlaceOrderHandler {
    public OrderId handle(PlaceOrderCommand cmd) {
        Order order = orderFactory.create(cmd.customer(), cmd.items());
        orderRepo.save(order);
        eventBus.publish(new OrderPlaced(order.id(), cmd.customer(), order.total()));
        return order.id();
    }
}

2. Query Model Pattern

AspectDetail
PurposeOptimized for reads; no business rules
SchemaDenormalized; one table per UI screen often
ReturnsDTOs shaped for client
StorageRDBMS, ES, Redis — whatever fits read pattern

3. Read Models Pattern

TypeUse Case
List ViewPaginated table of orders
Detail ViewFull order with line items, customer, shipping
Aggregated ViewSales by day, top products
Search IndexFull-text searchable view

4. Event Sourcing with CQRS

ComponentRole
Event StoreSole source of truth (write side)
AggregateRebuilt from events; processes commands → new events
ProjectionSubscribes to event stream → builds read models
Query APIReads from projections only

ES + CQRS Flow

[Command] → [Aggregate] → [Event Store] → [Projection] → [Read Model] → [Query]
                                              ↓
                                       [Other Consumers]
        

5. Separate Read/Write Databases

SideStorage Choice
WritePostgreSQL with strong consistency, normalization
ReadElasticsearch (search), Redis (low-latency lookup), Materialized RDBMS
SyncEvents / CDC; eventual consistency
ScaleIndependent scale and tech choice per side

6. Projection Pattern

PropertyDetail
Function(currentReadModel, event) → newReadModel
IdempotentRequired since events may replay
Offset TrackingPersist last-processed event ID/offset
RebuildReset offset → replay full stream

7. Snapshot Pattern

AspectDetail
PurposeAvoid replaying entire event history on every load
MechanismPeriodic snapshot of aggregate state at version N
LoadLoad snapshot + events after N
FrequencyEvery X events (e.g., 100) or time-based

8. Command Handler Pattern

StepAction
1. ValidateSchema, authorization, format
2. Load AggregateFrom repo (or replay events)
3. Apply CommandBusiness rules; produce events
4. PersistSave aggregate / append events
5. PublishEmit events via outbox

9. Query Handler Pattern

AspectDetail
InputQuery DTO (filter, paging, sorting)
OutputRead DTO directly from read store
No Side EffectsPure function over read model
CachingOften safe to cache aggressively

Example: Query Handler

public record OrderListQuery(CustomerId customer, int page, int size) {}

@Component
public class OrderListHandler {
    public Page<OrderSummaryDto> handle(OrderListQuery q) {
        return orderViewRepo.findByCustomer(q.customer(), 
            PageRequest.of(q.page(), q.size(), Sort.by("placedAt").descending()));
    }
}

10. Eventually Consistent Queries

UX StrategyDetail
Optimistic UpdateShow new state in UI before server confirms
Indicator"Saving..." spinner or syncing badge
Read-after-WriteCache write locally for the writing client
PollingRe-fetch until projection catches up
Versioned ReadsPass written version; wait until read store catches up