Command and Query Responsibility Segregation (CQRS) Pattern 命令和查询职责分离(CQRS)模式
Segregate operations that read data from operations that update data by using separate interfaces. This pattern can maximize performance, scalability, and security; support evolution of the system over time through higher flexibility; and prevent update commands from causing merge conflicts at the domain level.
通过使用独立的接口从操作读取数据更新数据的聚合操作。这种模式可以最大限度地提高性能,可扩展性和安全性;支持该系统通过上更大的灵活性时间演变;并防止造成合并在域级别冲突升级的命令。
Context and Problem 背景和问题
In traditional data management systems, both commands (updates to the data) and queries (requests for data) are executed against the same set of entities in a single data repository. These entities may be a subset of the rows in one or more tables in a relational database such as SQL Server.
在传统的数据管理系统中,这两个命令(更新数据),并查询(数据请求)是针对在一个单一的数据存储库中的相同的一组实体的执行。这些实体可以是在关系数据库中的一个或多个表如SQL Server的行的子集。
Typically, in these systems, all create, read, update, and delete (CRUD) operations are applied to the same representation of the entity. For example, a data transfer object (DTO) representing a customer is retrieved from the data store by the data access layer (DAL) and displayed on the screen. A user updates some fields of the DTO (perhaps through data binding) and the DTO is then saved back in the data store by the DAL. The same DTO is used for both the read and write operations, as shown in Figure 1.典型地,在这些系统中,所有的创建,读取,更新和删除(CRUD)操作被施加到该实体的相同的表示。例如,代表一个客户的数据传输对象(DTO)从数据存储器由数据访问层(DAL)检索并显示在屏幕上。用户更新DTO的某些字段(可能通过数据绑定),然后DTO由DAL保存回在数据存储中。相同DTO既用于读操作和写操作,如图1。
Figure 1 - A traditional CRUD architecture 图1 - 传统的CRUD架构
Traditional CRUD designs work well when there is only limited business logic applied to the data operations. Scaffold mechanisms provided by development tools can create data access code very quickly, which can then be customized as required.
传统的CRUD设计工作良好时,有施加到数据操作只有限的业务逻辑。由开发工具提供可以非常快速地创建数据访问代码脚手架机制,根据需要,可以再进行定制。
However, the traditional CRUD approach has some disadvantages:
然而,传统的CRUD方法有一些缺点:
- It often means that there is a mismatch between the read and write representations of the data, such as additional columns or properties that must be updated correctly even though they are not required as part of an operation.
- 这往往意味着数据的读写之间不匹配,例如,必须正确更新的附加列或属性,即使它们不需要作为操作的一部分。
- It risks encountering data contention in a collaborative domain (where multiple actors operate in parallel on the same set of data) when records are locked in the data store, or update conflicts caused by concurrent updates when optimistic locking is used. These risks increase as the complexity and throughput of the system grows. In addition, the traditional approach can also have a negative effect on performance due to load on the data store and data access layer, and the complexity of queries required to retrieve information.
- 在合作领域遇到数据冲突时是有风险的(多个角色在同一组数据的并行操作)当记录被锁定在数据存储区,或更新引起冲突被并行更新用使用乐观锁。这些风险增加系统的复杂性和吞吐量。此外,传统的方法也可以对性能由于数据存储和数据访问层上加载有负面影响,并检索信息需要查询的复杂性。
- It can make managing security and permissions more cumbersome because each entity is subject to both read and write operations, which might inadvertently expose data in the wrong context.
- 它可以使管理安全和权限比较繁琐,因为每个实体受到读取和写入操作,这可能无意中在错误的情况下公开数据。
Note:注意:
For a deeper understanding of the limits of the CRUD approach see “CRUD, Only When You Can Afford It” on MSDN.
Solution 解决方案
Command and Query Responsibility Segregation (CQRS) is a pattern that segregates the operations that read data (Queries) from the operations that update data (Commands) by using separate interfaces. This implies that the data models used for querying and updates are different. The models can then be isolated, as shown in Figure 2, although this is not an absolute requirement.
命令和查询职责分离(CQRS)是一个模式通过通过独立接口分离数据(查询)的操作从更新数据(命令)操作。这意味着,用于查询和更新的数据模型是不同的。然后该模型可以被分离,如图2中,虽然这不是绝对的要求。
Figure 2 - A basic CQRS architecture 图2 - 一个基本的架构CQRS
Compared to the single model of the data (from which developers build their own conceptual models) that is inherent in CRUD-based systems, the use of separate query and update models for the data in CQRS-based systems considerably simplifies design and implementation. However, one disadvantage is that, unlike CRUD designs, CQRS code cannot automatically be generated by using scaffold mechanisms.
相比数据的单一模型(从开发者建立他们自己的概念模型)是固有的在CRUD为基础的系统,使用单独的查询和更新的模型用于基于CQRS系统中的数据显着地简化设计和实施。然而,一个缺点是,不像CRUD设计,CQRS代码不能自动通过使用架构机制所产生。
The query model for reading data and the update model for writing data may access the same physical store, perhaps by using SQL views or by generating projections on the fly. However, it is common to separate the data into different physical stores to maximize performance, scalability, and security; as shown in Figure 3.
读取数据和写入数据的更新模型的查询模式可以访问相同的物理存储,也许通过使用SQL视图或通过在操作中产生投影。然而,这是常见的数据分成不同的物理存储来提高性能,可扩展性和安全性;如图3。
Figure 3 - A CQRS architecture with separate read and write stores 图3 - 一个CQRS结构,带独立读写存储
The read store can be a read-only replica of the write store, or the read and write stores may have a different structure altogether. Using multiple read-only replicas of the read store can considerably increase query performance and application UI responsiveness, especially in distributed scenarios where read-only replicas are located close to the application instances. Some database systems, such as SQL Server, provide additional features such as failover replicas to maximize availability.
读取的存储可以是写存储只读翻版,或读写存储可能有完全不同的结构。使用多个只读读取存储的副本可以显着提高查询性能和应用程序的UI响应速度,特别是在分布式场景中只读副本是位于靠近应用实例。一些数据库系统,如SQL Server,提供额外的功能,如故障切换副本,以最大限度地提高可用性。
Separation of the read and write stores also allows each to be scaled appropriately to match the load. For example, read stores typically encounter a much higher load that write stores.
分离的读写存储允许每个规模适当匹配负载。例如,读存储通常会遇到一个更高的负载写存储。
When the query/read model contains denormalized information (see Materialized View Pattern), performance is maximized when reading data for each of the views in an application or when querying the data in the system.
当查询/读取模型包含非规范化的信息(见物化视图模式),性能是最大化当在应用程序读取为每个视图中的数据或查询系统中的数据时。
For more information about the CQRS pattern and its implementation, see the following resources:
有关CQRS模式及其实现的更多信息,请参阅以下资源:
- The patterns & practices guide CQRS Journey on MSDN. In particular you should read the chapter Introducing the Command Query Responsibility Segregation Pattern for a full exploration of the pattern and when it is useful, and the chapter Epilogue: Lessons Learned to understand some of the issues that can arise when using this pattern.
- •该模式与实践指导CQRS Journey在MSDN上。尤其是你应该阅读的章节介绍命令查询职责分离模式该模式的全面勘探和当它是有用的,章结语:吸取的经验教训,了解一些问题,可能会出现时在使用这种模式。
- The post CQRS by Martin Fowler, which explains the basics of the pattern and links to several other useful resources.
- 由Martin Fowler发表的CQRS,解释了模式基础知识和其他一些有用的资源链接。
- Greg Young’s posts on the Code Better website, which explore many aspects of the CQRS pattern.
- Greg Young's发表在Code Better网站,它的许多方面CQRS模式探讨。
Issues and Considerations 问题和注意事项
Consider the following points when deciding how to implement this pattern:
在决定如何实现这个模式时,考虑以下几点:
- Dividing the data store into separate physical stores for read and write operations can increase the performance and security of a system, but it can add considerable complexity in terms of resiliency and eventual consistency. The read model store must be updated to reflect changes to the write model store, and it may be difficult to detect when a user has issued a request based on stale read data—meaning that the operation cannot be completed.
将数据存储到不同的物理存储的读写操作可以提高系统的性能和安全性,但它可以在弹性和最终一致性方面增加相当大的复杂性。读取模型存储必须进行更新以反映写模型存储的变化,当用户发出请求的基础上不具有过期的读数据意味着操作不能完成时,它可能是难以检测到的。
-
Note:注意: For a description of eventual consistency see the Data Consistency Primer.
- Consider applying CQRS to limited sections of your system where it will be most valuable, and learn from the experience.
- 考虑申请CQRS到系统的各个部分限制在那里将是最有价值的,从经验中学习。
- A typical approach to embracing eventual consistency is to use event sourcing in conjunction with CQRS so that the write model is an append-only stream of events driven by execution of commands. These events are used to update materialized views that act as the read model. For more information see Event Sourcing and CQRS.
- 到拥抱最终一致性的典型的方法是使用事件源与CQRS结合使写模式是由命令的执行驱动事件的唯一附加流。这些事件被用于更新作为读模式物化视图。欲了解更多信息,请参阅事件采购与CQRS。
When to Use this Pattern 何时使用这种模式
This pattern is ideally suited to:
这种模式非常适合:- Collaborative domains where multiple operations are performed in parallel on the same data. CQRS allows you to define commands with a sufficient granularity to minimize merge conflicts at the domain level (or any conflicts that do arise can be merged by the command), even when updating what appears to be the same type of data.
- 协作域其中多个操作并行地对相同的数据进行。CQRS允许你用足够的粒度定义命令,以尽量减少在域级别的合并冲突(或确实出现了可以通过在命令合并的任何冲突),更新似乎是同一类型的数据也是如此。
- Use with task-based user interfaces (where users are guided through a complex process as a series of steps), with complex domain models, and for teams already familiar with domain-driven design (DDD) techniques. The write model has a full command-processing stack with business logic, input validation, and business validation to ensure that everything is always consistent for each of the aggregates (each cluster of associated objects that are treated as a unit for the purpose of data changes) in the write model. The read model has no business logic or validation stack and just returns a DTO for use in a view model. The read model is eventually consistent with the write model.
- 与基于任务的用户界面(其中用户通过一个复杂的过程引导作为一系列步骤),具有复杂的领域模型,并且为团队已经熟悉领域驱动设计(DDD)技术使用。写入模型具有与业务逻辑、输入验证和商务验证一个完整的命令处理栈,以确保一切总是为每个聚集体(即被视为一个单元,用于数据变化的目的相关联的对象的每个集群相一致)在写模式。读模型没有业务逻辑或验证码,只是返回在视图模型使用DTO。读模型是最终与写模型一致。
- Scenarios where performance of data reads must be fine-tuned separately from performance of data writes, especially when the read/write ratio is very high, and when horizontal scaling is required. For example, in many systems the number of read operations is orders of magnitude greater that the number of write operations. To accommodate this, consider scaling out the read model, but running the write model on only one or a few instances. A small number of write model instances also helps to minimize the occurrence of merge conflicts.
- 方案其中数据的性能读取必须从数据的性能分别微调写入,尤其是当读/写比是非常高的,并且当需要的水平缩放。例如,在许多系统读取操作的数目是大于的写操作的数目的数量级。为了适应这一点,考虑向外扩展的读模式,而是只在一个或几个实例中运行的写模式。写入模型实例少数也有助于减少合并冲突的发生。
- Scenarios where one team of developers can focus on the complex domain model that is part of the write model, and another less experienced team can focus on the read model and the user interfaces.
- 场景中的开发团队可以专注于写模式的一部分的复杂的域模型,另一个缺乏经验的团队可以专注于读取模型和用户界面。
- Scenarios where the system is expected to evolve over time and may 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 should not affect the availability of the others.
- 与其他系统集成,特别是与Event Sourcing合并一起,其中一个子系统的temporal故障不会影响其他的可用性的组合。
This pattern might not be suitable in the following situations:
这种模式可能不适合在以下几种情况:
- Where the domain or the business rules are simple.
- 域或业务规则很简单。
- Where a simple CRUD-style user interface and the related data access operations are sufficient.
- 其中,一个简单的CRUD风格的用户界面和相关的数据访问操作是足够的。
- For implementation across the whole system. There are specific components of an overall data management scenario where CQRS can be useful, but it can add considerable and often unnecessary complexity where it is not actually required.
- 在整个系统的实施。有特定的组件的整体数据管理的情况下,CQRS可以是有用的,但它可以增加相当大的和经常不必要的复杂性,它实际上并不需要。
Event Sourcing and CQRS 事件源和CQRS
The CQRS pattern is often used in conjunction with the Event Sourcing pattern. CQRS-based systems use separate read and write data models, each tailored to relevant tasks and often located in physically separate stores. When used with Event Sourcing, the store of events is the write model, and this is the authoritative 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 interfaces and display requirements of the application, which helps to maximize both display and query performance.
的CQRS模式常常与事件源模式相结合。基于CQRS的系统使用不同的读取和写入数据的模型,每一个针对相关任务,并常位于物理上独立的存储。当使用事件源时,事件的存储是写模型,这是信息的权威来源。一种基于CQRS系统读取模型提供数据的物化视图,通常是高度规范化的意见。这些视图是针对应用程序的接口和显示要求的,它有助于最大限度地显示和查询性能。
Using the stream of events as the write store, rather than the actual data at a point in time, avoids update conflicts on a single aggregate and maximizes performance and scalability. The events can be used to asynchronously generate materialized views of the data that are used to populate the read store.
使用事件作为写入存储,而不是实际的数据的流在一个时间点,避免了在单个聚合更新冲突并最大限度地提高的性能和可扩展性。该事件可用于异步生成用于填充所读取存储器中的数据的物化视图。
Because the event store is the authoritative 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. The materialized views are effectively a durable read-only cache of the data.
由于事件存储是信息的权威来源,因此可以删除物化视图,并重放所有过去的事件来创建一个新的表示当前状态的系统时,或当读取模型必须改变。物化视图是数据的持久只读缓存。
When using CQRS combined with the Event Sourcing pattern, consider the following:
当使用CQRS结合事件源模式,考虑以下:
- As with any system where the write and read stores are separate, systems based on this pattern are only eventually consistent. There will be some delay between the event being generated and the data store that holds the results of operations initiated by these events being updated.
- 与其中写入和读取存储是分开的任何系统的基础上,这种模式的系统只有最终一致。会有被生成的事件,并且保存由这些事件被更新启动的操作的结果的数据存储之间有一些延迟。
- The pattern introduces additional complexity because code must be created to initiate and handle events, and assemble or update the appropriate views or objects required by queries or a read model. The inherent complexity of the CQRS pattern when used in conjunction with Event Sourcing can make a successful implementation more difficult, and requires relearning of some concepts and a different approach to designing systems. However, Event Sourcing 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.
- 该模式引入了额外的复杂性,因为代码必须创建发起和处理事件,并组装或者更新查询或读取模型所需的相应的视图或对象。与Event Sourcing配合使用CQRS模式的固有的复杂性时可以使一个成功实施较困难,且需要重新学习的一些概念和一个不同的方法来设计的系统。然而,Event Sourcing可以更容易的建模在域中,并且可以更容易地重建观点或因为在数据中的变化的意图保留创建新的。
- Generating materialized views for use in the read model or projections of the data by replaying and handling the events for specific entities or collections of entities may require considerable processing time and resource usage, especially if it requires summation or analysis of values over long time periods, because all the associated events may need to be examined. This may be partially resolved by implementing snapshots of the data at scheduled intervals, such as a total count of the number of a specific action that have occurred, or the current state of an entity.
- 生成物化视图中使用的重放和特定的实体或实体的集合处理事件数据的读取模型或预测可能需要大量的处理时间和资源的使用,特别是如果它需要总结或分析值在长时间内,因为所有相关的事件可能需要检查。这可能部分地通过在预定的时间间隔中执行数据的快照部分来解决,如一个已发生的特定动作的总数,或一个实体的当前状态。
Note:注释:
For more information see Event Sourcing Pattern and Materialized View Pattern, and the patterns & practices guide CQRS Journey on MSDN. In particular you should read the chapter Introducing Event Sourcing for a full exploration of the pattern and how it is useful with CQRS, and the chapter A CQRS and ES Deep Dive to understand more—including how aggregate partitioning can be used with CQRS in Microsoft Azure.
Example 例子
The following code shows some extracts from an example of a CQRS implementation, which uses different definitions for the read and the write models. The model interfaces do not dictate any features of the underlying data stores, and they can evolve and be fine-tuned independently because these interfaces are separated.
下面的代码显示一个CQRS实现例子的摘录,它使用不同的定义的读取和写入模式的一个例子一些提取物。该模型的接口没有规定底层数据存储的任何功能,他们可以发展和进行独立微调,因为这些接口是分开独立的。
The following code shows the read model definition.
下面的代码显示读取模型定义。
// Query interface namespace ReadModel { public interface ProductsDao { ProductDisplay FindById(int productId); IEnumerable<ProductDisplay> FindByName(string name); IEnumerable<ProductInventory> FindOutOfStockProducts(); IEnumerable<ProductDisplay> FindRelatedProducts(int productId); } public class ProductDisplay { public int ID { get; set; } public string Name { get; set; } public string Description { get; set; } public decimal UnitPrice { get; set; } public bool IsOutOfStock { get; set; } public double UserRating { get; set; } } public class ProductInventory { public int ID { get; set; } public string Name { get; set; } public int CurrentStock { get; set; } } }
The system allows users to rate products. The application code does this by using the RateProduct command shown in the following code.
该系统允许用户对产品进行评分。应用程序代码使用下面的代码显示的RateProduct命令执行此操作。
public interface Icommand { Guid Id { get; } } public class RateProduct : Icommand { public RateProduct() { this.Id = Guid.NewGuid(); } public Guid Id { get; set; } public int ProductId { get; set; } public int rating { get; set; } public int UserId {get; set; } }
The system uses the ProductsCommandHandler class to handle commands sent by the application. Clients typically send commands to the domain through a messaging system such as a queue. The command handler accepts these commands and invokes methods of the domain interface. The granularity of each command is designed to mitigate the chance of conflicting requests. The following code shows an outline of the ProductsCommandHandler class.
该系统采用的ProductsCommandHandler类来处理应用程序发送的命令。客户通常通过消息系统发送命令到域,如队列。命令处理程序接受这些命令,并调用域接口的方法。每个命令的粒度被设计为减轻冲突请求的机会。下面的代码显示了ProductsCommandHandler类的轮廓。
public class ProductsCommandHandler : ICommandHandler<AddNewProduct>, ICommandHandler<RateProduct>, ICommandHandler<AddToInventory>, ICommandHandler<ConfirmItemShipped>, ICommandHandler<UpdateStockFromInventoryRecount> { private readonly IRepository<Product> repository; public ProductsCommandHandler (IRepository<Product> repository) { this.repository = repository; } void Handle (AddNewProduct command) { ... } void Handle (RateProduct command) { var product = repository.Find(command.ProductId); if (product != null) { product.RateProuct(command.UserId, command.rating); repository.Save(product); } } void Handle (AddToInventory command) { ... } void Handle (ConfirmItemsShipped command) { ... } void Handle (UpdateStockFromInventoryRecount command) { ... } }
The following code shows the ProductsDoman interface from the write model.
下面的代码显示了来自写模型的ProductsDoman接口。
public interface ProductsDomain { void AddNewProduct(int id, string name, string description, decimal price); void RateProduct(int userId int rating); void AddToInventory(int productId, int quantity); void ConfirmItemsShipped(int productId, int quantity); void UpdateStockFromInventoryRecount(int productId, int updatedQuantity); }
Also notice how the ProductsDomain interface contains methods that have a meaning in the domain. Typically, in a CRUD environment these methods would have generic names such as Save or Update, and have a DTO as the only argument. The CQRS approach can be better tailored to suit the way that this organization carries out business and inventory management.
还注意到ProductsDomain接口包含有域中的意义方法。通常,在一个CRUD环境这些方法将通用名称如Save或Update,并有一个DTO作为唯一的参数。CQRS方法可以更好地适合这个组织进行业务和库存管理方式。
Related Patterns and Guidance 相关模式和指导
- Data Consistency Primer. This guidance explains the issues that are typically encountered due to eventual consistency between the read and write data stores when using the CQRS pattern, and how these issues can be resolved.
- Data Partitioning Guidance. This guidance describes how the read and write data stores used in the CQRS pattern can be divided into separate partitions that can be managed and accessed separately to improve scalability, reduce contention, and optimize performance.
- Event Sourcing Pattern. This pattern describes in more detail how Event Sourcing can be used with the CQRS pattern to simplify tasks in complex domains; improve performance, scalability, and responsiveness; provide consistency for transactional data; and maintain full audit trails and history that may enable compensating actions.
- Materialized View Pattern. The read model of a CQRS implementation may contain materialized views of the write model data, or the read model may be used to generate materialized views.
More Information 更多信息
- The patterns & practices guide CQRS Journey on MSDN.
- The article CQRS on Martin Fowler's blog.
- The articles by Greg Young’s on the Code Better website.