零度编程命令和查询责任分离 (CQRS) 模式( 二 )


实施这种模式的一些挑战包括:复杂性。CQRS 的基本理念十分简单 。但它可能导致应用程序的设计更为复杂 , 尤其在包含事件溯源模式时 。 消息。虽然 CQRS 不需要消息 , 但它通常会使用消息处理命令和发布更新事件 。在此情况下 , 应用程序必须处理消息失败或重复的消息 。 最终一致性。如果分离读取和写入数据库 , 读取数据可能会过时 。必须更新读取模型存储以反映对写入模型存储的更改 , 并且很难检测用户何时发布了基于陈旧读取数据的请求 。
请考虑以下方案的 CQRS:许多用户并行访问相同数据的协作域 。CQRS 允许您定义粒度足够的命令 , 以尽量减少域级别的合并冲突 , 并且该命令可以合并出现的冲突 。 基于任务的用户界面 , 用户在该界面可按照一系列步骤组成的复杂过程指南或通过复杂域模型指南来操作 。写入模型具有完整的命令处理堆栈 , 具有业务逻辑、输入验证和业务验证 。写入模型可以将一组关联对象视为数据更改的单个单元(在 DDD 术语中为聚合) , 并确保这些对象始终处于一致状态 。读取模型没有业务逻辑或验证堆栈 , 只需返回一个 DTO 才能在视图模型中使用 。读取模型最终与写入模型保持一致 。 数据读取性能必须与数据写入性能分开微调的方案 , 尤其是在读取次数远远大于写入次数时 。在这种情况下 , 可以横向扩展读取模型 , 但仅在几个实例上运行写入模型 。一小部分写入模型实例还有助于最大程度减少合并冲突 。 应用场景:一个开发团队可专注于复杂域模型(作为写入模型一部分) , 而另一团队可专注于读取模型和用户界面 。 应用场景:系统会随着时间不断演变 , 并且可能会包含多个版本的模型 , 或业务规则会定期更改 。 与其他系统集成时(尤其是与事件溯源集成时) , 一个子系统的临时故障错误不允许影响其他子系统的可用性 。
对于以下情况不建议使用此模式:域或业务规则很简单 。 简单的 CRUD 风格的用户界面和数据访问操作就足够了 。
请考虑将 CQRS 应用于系统中最能实现其价值的有限部分 。
事件溯源和 CQRS
CQRS 模式通常与事件溯源模式一起使用 。 基于 CQRS 的系统使用分离的读取和写入数据模型 , 每个模型针对相关任务定制 , 并且通常位于物理分离存储中 。 当与事件源模式一起使用时 , 事件的存储是写入模型 , 是官方的信息来源 。 基于 CQRS 系统的读取模型提供数据的具体化视图 , 通常是高度非规范化视图 。 针对应用程序的接口和显示要求定制这些视图 , 这有助于最大限度地提高显示和查询性能 。
使用事件流作为写入存储(而不是使用某个时间点的实际数据) , 这可避免单个聚合上的更新冲突 , 并最大限度提高性能和可扩展性 。 事件可以用于以异步方式生成用于填充读取存储的数据具体化视图 。
由于事件存储是官方信息源 , 因此可删除具体化视图并重放所有过去事件 , 以便在系统升级时或必需更改读取模型时创建当前状态的新表示法 。 具体化视图实际上是数据的持久只读缓存 。
当结合使用 CQRS 和事件溯源模式时 , 请考虑以下方面:在任何写入和读取存储分离的系统中 , 基于此模式的系统只会最终一致 。正在生成的事件与正在更新的数据存储之间的存在一定延迟 。 本模式会增加复杂性 , 因为必需创建代码以启动和处理事件 , 组合或更新查询或读取模型所需的适当视图或对象 。结合事件溯源模式使用时 , CQRS 模式的复杂性会使实现难以顺利完成 , 需要使用设计系统的其他方法 。但是 , 事件溯源可以更加轻松地对域创建模型 , 从而可以很方便地重新生成视图或创建新视图 , 因为它保留了想要执行的数据更改 。 通过重放和处理特定实体或实体集合的事件来生成用于读取模型或数据投影的具体化视图可能需要大量的处理时间和资源 。特别是当如果需要长时间求和或分析值时 , 因为需要检查所有相关的事件 。通过以计划的时间间隔(例如已发生的特定操作的总计数或实体的当前状态)实现数据快照来解决此问题 。


推荐阅读