■命令和查询责任分离 (CQRS) 模式

命令和查询责任分离 (CQRS) 模式分隔数据存储的读取和更新操作 。在应用程序中实现 CQRS 可以最大限度地提高其性能、可扩展性和安全性 。迁移到 CQRS 创建的灵活性使系统能够随着时间的推移更好地发展 , 并防止更新命令在域级别引发合并冲突 。
问题
在传统的体系结构中 , 使用同一数据模型查询和更新数据库 。这十分简单 , 非常适用于基本的 CRUD 操作 。但是 , 在更复杂的应用程序中 , 此方法会变得难以操作 。例如 , 在读取方面 , 应用程序可能执行大量不同的查询 , 返回具有不同形状的数据传输对象 (DTO) 。对象映射可能会变得复杂 。在写入方面 , 模型可能实施复杂验证和业务逻辑 。结果 , 模型执行太多操作 , 过度复杂 。
读写工作负载通常是不对称的 , 具有非常不同的性能和规模要求 。
■命令和查询责任分离 (CQRS) 模式
文章图片

文章图片

数据的读写表示形式之间通常不匹配 , 例如必须正确更新的其他列或属性 , 即使这些列在操作的一部分不需要 。当在同一组数据上并行执行操作时 , 可能会发生数据争用 。由于数据存储和数据访问层的加载以及检索信息所需的查询的复杂性 , 传统方法可能会对性能产生负面影响 。管理安全性和权限可能会变得复杂 , 因为每个实体都受读取和写入操作的约束 , 这可能会在错误的上下文中公开数据 。
CQRS 将读取和写入分离到不同的模型中 , 使用 命令更新 数据 , 查询用于读取数据 。命令应基于任务 , 而非以数据为中心 。("预订酒店客房" , 而不是"将预订状态设置为保留") 。命令可以放置在队列中进行异步处理 , 而不是同步处理 。查询从不修改数据库 。查询返回的 DTO 不封装任何域知识 。
然后可以隔离模型 , 如下图所示 , 尽管这不是绝对要求 。
■命令和查询责任分离 (CQRS) 模式
文章图片

文章图片

具有单独的查询和更新模型可简化设计和实现 。但是 , 一个缺点是 CQRS 代码不能使用基架机制(如 O/RM 工具)从数据库架构自动生成 。
为更好地实现隔离 , 可将读取数据与写入数据通过物理方式分离 。在此情况下 , 读取数据库可使用自己的已针对查询进行优化的数据架构 。例如 , 它可以存储数据的具体化视图 , 从而避免复杂联接或复杂 O/RM 映射 。它甚至可能使用不同类型的数据存储 。例如 , 写入数据库可能是关系数据库 , 而读取数据库是文档数据库 。
如果使用单独的读写数据库 , 则必须保持同步 。通常 , 每当编写模型更新数据库时 , 都会发布事件 , 从而完成这一目的 。必须在一个事务中更新数据库和发布事件 。
■命令和查询责任分离 (CQRS) 模式
文章图片

文章图片

读取存储可以是写入存储的只读副本 , 或者读取和写入存储可以具有完全不同的结构 。使用多个只读副本可以提高查询性能 , 尤其是在只读副本位于应用程序实例附近的分布式方案中 。
读取和写入存储的分离还允许彼此适当地缩放以匹配负载 。例如 , 读取存储通常会遇到高于写入存储的负载 。
某些 CQRS 实现使用事件溯源模式 。在此模式中 , 应用程序状态存储为事件序列 。每个事件表示对数据所作的一系列更改 。通过重播事件构造当前状态 。在 CQRS 上下文中 , 事件溯源的一个好处是 , 可以使用相同的事件通知其他组件 — 特别是通知读模型 。读模型使用事件创建当前状态的快照 , 这对查询而言更高效 。但是 , 事件溯源增加了设计的复杂度 。


推荐阅读