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 validation
  • AssignCase - Assigns case to an agent, enforces business rules
  • AddComment - Adds a comment, updates case state
  • ResolveCase - Transitions case to resolved state

Read model (Queries):

  • GetAgentQueue - Fast query for agent's assigned cases
  • GetCaseHistory - Timeline view optimized for display
  • SearchCases - Full-text search across cases
  • GetDashboardStats - 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:

  1. Write operations: Actions trigger transitions, which generate events. Events are the source of truth.
  2. Event processing: Projections subscribe to events and update themselves as events occur.
  3. Eventual consistency: Projections can lag slightly behind the write model, trading consistency for performance.
  4. 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:

Explore more patterns

This pattern is one of many for building collaborative systems. Check out other patterns or dive deeper into the framework components.