Pattern
CQRS (Command Query Responsibility Segregation)
Separate read and write operations into distinct models and paths. This pattern optimizes for different performance characteristics and allows read and write sides to evolve independently in collaborative systems.
Part 1: CQRS Pattern
Overview
CQRS separates the model for reading data from the model for writing data. Instead of using a single data model for both operations, you maintain separate models optimized for their specific purposes.
In collaborative systems, this pattern is particularly powerful because read and write operations often have very different requirements. Write operations need to enforce business rules, validate transitions, and maintain consistency. Read operations need to be fast, support complex queries, and present data in formats optimized for different user contexts.
When to use this pattern
Use CQRS when you have:
- Different read and write scales: Your system reads far more than it writes, or vice versa
- Complex queries: Read operations need to aggregate, filter, or join data in ways that would complicate the write model
- Different performance requirements: Reads need to be fast and cached, while writes need strong consistency
- Multiple read models: Different users or contexts need the same data presented in different formats
- Event sourcing: You're using events as the source of truth and deriving read models from them
- Team boundaries: Different teams can own read and write models independently
Avoid CQRS for simple CRUD applications where the overhead of maintaining two models isn't justified, or when strong consistency between reads and writes is critical.
Core concepts
Command side (Write model)
The write model handles all state-changing operations. It enforces business rules, validates transitions, and maintains consistency. Commands are operations that change state, and they typically result in events being generated.
Query side (Read model)
The read model is optimized for querying and presentation. It can be denormalized, cached, and structured specifically for the needs of different user interfaces or reporting requirements. Queries never change state.
Synchronization
The read model is kept in sync with the write model, typically through events or change streams. This synchronization can be eventual, allowing the read model to lag slightly behind the write model in exchange for better performance.
Projections
Read models are often implemented as projections—derived views computed from the write model's events or state. Multiple projections can exist for different purposes, all derived from the same source of truth.
Example: Case management system
Consider a case management system where agents handle customer support cases:
Write model (Commands):
CreateCase- Creates a new case with validationAssignCase- Assigns case to an agent, enforces business rulesAddComment- Adds a comment, updates case stateResolveCase- Transitions case to resolved state
Read model (Queries):
GetAgentQueue- Fast query for agent's assigned casesGetCaseHistory- Timeline view optimized for displaySearchCases- Full-text search across casesGetDashboardStats- Aggregated metrics for managers
The write model ensures cases can only be assigned to available agents and enforces state transitions. The read model is optimized for fast queries, supports complex filters, and can be cached without affecting write consistency.
Benefits
- Independent scaling: Read and write models can be scaled independently based on their specific load patterns
- Optimized models: Each model is optimized for its purpose without compromising the other
- Simpler write model: Write model doesn't need to support complex query requirements
- Multiple read views: You can create different read models for different use cases without affecting writes
- Team autonomy: Different teams can own and evolve read and write models independently
- Performance: Read models can be heavily cached and denormalized for speed
Part 2: CQRS in the State-Based Collaboration Framework
How CQRS fits in the framework
CQRS aligns naturally with the State-Based Collaboration Framework. The framework's architecture inherently separates read and write concerns through its component structure.
The write model in the framework consists of:
- States: The current state of collaborative objects
- Transitions: How state changes are defined and validated
- Actions: User operations that trigger state changes
- Events: Records of what happened, forming the source of truth
- Action-Control Policies: Rules about who can do what
The read model in the framework consists of:
- Projections: Read-optimized views derived from states and events
- Filters: Context-specific views that show relevant subsets of data
- Data-Access Policies: Rules about what data each user can see
Projections as read models
The framework's Projections component implements the read side of CQRS. Projections are derived, read-optimized views computed from the write model (states and events).
Projections in the framework:
- Are computed from events or current state, not directly written to
- Can be denormalized for fast querying
- Can be cached without affecting write consistency
- Can be optimized for specific use cases (dashboards, reports, timelines)
- Can lag behind the write model (eventual consistency) for better performance
Multiple projections can exist for the same underlying data, each optimized for different purposes, without affecting the write model.
Synchronizing read and write models
In the framework, the read model (projections) is kept in sync with the write model through events:
- Write operations: Actions trigger transitions, which generate events. Events are the source of truth.
- Event processing: Projections subscribe to events and update themselves as events occur.
- Eventual consistency: Projections can lag slightly behind the write model, trading consistency for performance.
- Replay capability: Since projections are derived from events, they can be rebuilt by replaying the event stream.
This approach ensures that the write model remains the single source of truth, while read models can be optimized, cached, and scaled independently.
Related patterns
This pattern works well with:
- State Machine Design Pattern - Use state machines in the write model to enforce transitions
- Framework overview - Learn about projections and events components
Explore more patterns
This pattern is one of many for building collaborative systems. Check out other patterns or dive deeper into the framework components.