背景
在传统的数据库管理系统中,执行更新和查询的是针对同一个数据库中的相同实体。当数据操作应用于简单的业务逻辑时,传统的CRUD设计方式工作良好。代码生成工具提供快速创建数据访问代码框架机制,然后根据需要进行定制扩展。
但是,传统的CRUD方法存在一点缺点:
- 它通用意味着数据的读取并显示的部分和写入到存储之间会不匹配。例如,即使一些附带的列或属性并没有最终显示在界面上,但更新时也要求对这些列和属性正确更新;
- 当记录被锁定在数据存储器中,或者当使用乐观锁解决并发更新而引起更新冲突时,会遭遇数据的争用,而且其风险随着系统的复杂度和吞吐量的增长而增加。此外,在数据存储和数据访问层上加载数据并执行复杂查询会在性能上产生负面影响;
- 使管理安全性和权限更麻烦,因为每个实体都会有读和写的操作,这可能会引起一些异常发生时错误上下文无意中暴露数据。
目的
命令和查询职责分离(CQRS)是指从更新数据的操作之外,使用单独的接口来读取数据。此模式可以最大化性能,具有可扩展和安全性,通过更高的灵活性支持系统扩展升级,并防止更新命令在领域级别引起合并冲突。
解决方案
命令和查询职责分离是将读取数据的操作通过使用单独接口,与更新数据的操作隔离。这意味着用于查询和更新的数据模型是不同的,然后就可以将模型隔离。与传统的CRUD单一模型相比面积与CQRS的系统中隔离的查询和更新模型大大简化了设计和实现。
用于读取数据的查询模型和用于写入的更新模型可能通过使用SQL视图或通过生成的投影(数据映射)访问统一物理存储。然而,通常将读写的数据存储在不同的物理存储中,以最大化性能、可扩展性和安全性。
读存储可以是写存储的只读副本,或者读存储和写存储可以具有完全不同的结构。使用读存储的多个只读副本可以显著提高查询性能和应用程序的响应速度,特别是只读副本位于应用程序实例附近的分布式场景中。
读写存储的分离还可以适当的进行配置以匹配各自负载,如读存储通常比写存储承担高得多的负载。
考虑因素
- 将数据存储划分为读写操作的单独物理存储,可提高系统性能和安全性,但在弹性和最终一致性方面却也增加了较大的复杂度。当更新写入数据时,需要在读取模型存储中得到体现,并且可能难以检测到用户是否是基于陈旧数据发出的请求,也就意味着请求不能正确完成;
- 采用最终一致性的典型方法是将事件源与CQRS结合使用,保证写入模型是由执行命令驱动的尽追加式事件流,这些事件流用于更新充当读取模型的实例化视图。
适用情况
适用情况:
- 对相同的数据并行执行多个操作的协同域。CQRS运行以足够的粒度定义更新命令,以最小化域级别的合并冲突,即使更新相同类型的数据;
- 在数据读性能必须与数据写入性能分开调整的情况下,特别是当读写比率非常高以及需要水平扩展时;
不适用情况:
- 业务规则很简单时;
- 简单的CRUD风格的用户界面和相关的数据访问操作足够时;
- 用于整个系统的实现时,可能会导致不必要的复杂性。