Skip to content

Latest commit

 

History

History
97 lines (63 loc) · 7.71 KB

File metadata and controls

97 lines (63 loc) · 7.71 KB

CQRS: Command and Query Responsibility Segregation

CQRS is a pattern that separates read and update operations for a data store. It allows a system to better evolve over time and prevents update commands from causing merge conflicts at the domain level.

Context and problem

In traditional architectures, the same data model is used to query and update a database. That's simple and works well for basic CRUD operations.

However, as the system evolves, the data model and the queries become more complex. On the read side, the application may perform many different queries, returning DTOs with different shapes. On the write side, the model may implement complex validation and business logic.

  • There is often a mismatch between the read and write representations of data, such as additional columns or properties that must be updated correctly even though they aren't required as part of an operation.
  • Data contention can occur when operations are performed in parallel on the same set of data.
  • Performance can be affected due to load on the data store and data access layer, and the complexity of the queries requried to retrieve information.
  • Managing security and permissions can become complex, because each entity is subject to both read and write operations, which might expose data in the wrong context.

Solution

CQRS separates reads and writes into different models, using commands to update data, and queries to read data.o

  • Commands should be task-based, rather than data centric. (e.g. "Book hotel room", not "set ReservationStatus to Reserved").
  • Commands may be placed on a queue for asynchronous processing, rather than being processed synchronously.
  • Queries never modify the database. A query returns a DTO that does not encapsulate any domain knowledge.

The models can then be isolated, although that's not an absolute requirement.

Advantages

  • Independent scaling: CQRS allows the read and write workloads to scale independently, and may result in fewer lock contentions.
  • Optimized data schemas: The read side can use a schema that is optimized for queries, while the write side uses a schema that is optimized for updates.
  • Security: It's easier to ensure that only the right domain entities are performing writes on the data.
  • Separation of concerns: Segregating the read and write sides can result in models that are more maintainable and flexible. Most of the complex business logic goes into the write model. The read model can be relatively simple.
  • Simpler queries: By storing a materialized view in the read database, the application can avoid complex joins when querying.

Issues and considerations

  • Complexity: CQRS code can't automatically be generated from a database schema using caffolding mechanisms such as ORM tools. It can lead to more complex design and implementation, especially if they include the Event Sourcing pattern.
  • Messaging: It's common to use messaging to process commands and publish update events. Therefore, the application must handle message failures or duplicate messages.
  • Eventual consistency: If you separate the read and write databases, the read data may be stale. The read store must be updated to reflect changes to the write store, and it can be difficult to detect when a user has issued a request based on stale read data.

When to use

  • Collaborative domains where many users access the same data in parallel. CQRS allows you to define commands with enough granularity to minimize merge conflicts at the domain level, and conflicts that do arise can be merged by the command.
  • Task-based UI where users are guided through a complex process as a series of steps or with complex domain models. The wirte model has a full command-processing stack with business logic, input validation, and business validation.
  • Performance of data reads must be fine-tuned separately from performance of data writes, especially when the number of reads is much greater than the number of writes.
  • One team of developers can focus on the complex domain model for writes, while another team can focus on the read model and the user interfaces.
  • The system is expected to evolve over time and might contain multiple versions of the model, or where business rules change regularly.
  • Integration with other systems, especially in combination with event sourcing, where the temporal failure of one subsystem shouldn't affect the availability of the others.

When not to use

  • The domain or the business rules are simple.
  • A simple CRUD-style UI and data access operations are sufficient.

Consider applying CQRS to limited sections of your system where it will be most valuable.

Implementation details

Separate data stores

For greater isolation, you can physically separate the read data from the write data. In that case, the read database can use its own data schema that is optimized for queries.

For example, it can store a materialized view of the data, in order to avoid complex joins or complex ORM mappings. It might even use a different type of data store. For example, the write database might be relational, while the read database is a document database.

If separate read and write databases are used, they must be kept in sync. Typically this is accomplished by having the write model publish an event whenever it updates the database. This must occur in a single transaction.

See Event-driven architecture style.

Event sourcing

CQRS is often used along with the Event Sourcing pattern. The store of events is the write model, and is the official source of information. The read model of a CQRS-based system provides materialized views of the data, typically as highly denormalized views. These views are tailored to the UI, which helps to maximize both display and query performance.

Using the stream of events as the write store, rather than the actual data at a point in time, avoid updates conflicts on a single aggregate and maximizes performance and scalability. The events can be used to asynchronously generate materialized views of the data.

Because the event store is the official source of information, it is possible to delete the materialized views and replay all past events to create a new representation of the current state when the system evolves, or when the read model must change.

When using CQRS combined with Event Sourcing, consider the following:

  • Systems are only eventually consistent. There will be some delay between the event being generated and the data store being updated.
  • Adds complexity because code must be created to initiate and handle events, and assemble or update the appropriate views requires by the read model. However, it can make it easier to model the domain, and makes it easier to rebuild views or create new ones because the intent of the changes in the data is preserved in the event stream.
  • Generating materialized views for use in the read model by replaying and handling the events can require significant processing time and resource usage. Resolve this by implementing snapshots at scheduled intervals.