DDD、CQRS/ES 和微服务应该根据微服务的视图或聚合做出决定吗?
DDD, CQRS/ES & MicroServices Should Decisions be taken on Microservice's views or aggregates?
所以我将通过使用示例来解释问题,因为它使一切都更加具体,并希望能减少歧义。
架构非常简单
1 个微服务 <=> 1 个聚合 <=> 事务边界
每个微服务都将使用 CQRS/ES 设计模式,这意味着
- 每个微服务都有自己的聚合映射现实世界问题的领域
- 将从事件存储中重建聚合状态
- 每个事件都表示聚合内的状态更改,并将通过消息代理传输到对更改感兴趣的任何服务
- 每个微服务都将在其自己的域内进行事务处理
- 每个微服务最终都会与其他域保持一致
- 每个微服务都会根据其他微服务发出的事件构建自己的视图模型
所以这个例子假设我们有一个银行系统
current-account
微服务负责映射客户活期账户...取款,存款
rewards
微服务将负责对银行提供的任何奖励进行盘点和盘点
air-miles
微服务将负责监控来自 current-account
的所有交易,并通过我们的奖励微服务 奖励客户
所以问题是 air-miles
微服务是否应该根据自己的视图模型做出决策,该视图模型正在根据来自 current-account
的事件进行更新,同样地,选择它应该奖励的内容送给客户?
在本地视图模型上做出决策的缺点;
- 复制有关如何维护这些视图的域逻辑
- 视图中的错误可能会传播错误的奖励
- 损坏的视图模型上的状态更改(也就是发出的事件)可能会对其他服务产生影响,这些服务正在对这些事件做出自己的决定
在本地视图模型上做出决定的优势;
系统不需要不断查询拥有域的微服务
系统应该更快,资源占用更少
或者它应该使用来自服务的事件来触发对拥有域的聚合的查询,在这样做时我们接受视图模型可能损坏的事实,但最终决定应始终咨询拥有的聚合域名?
拜托,以上问题只是我对架构的理解,这个post的目的是就如何在微服务环境中有效地使用这个架构获得不同的看法,以保持每个服务分离但避免级联损坏场景,服务之间没有太多的喋喋不休。
So the problem is this Should the air-miles micro service take decisions based on its own view model which is being updated from events coming from the current-account, and similarly, on picking which reward it should give out to the Customer?
每个消费者使用生产者计算的表示的本地副本。
因此,如果 air-miles
需要来自 current-account
的信息,它应该查看由 current-account
服务计算的视图的本地副本。
关键思想是:微服务应该相互隔离;您应该能够重新设计和部署一个而不影响其他。
所以试试这个思想实验 - 假设我们有这三个微服务,但都保存当前状态的快照,而不是事件。一切正常,然后想象 current-account
维护者发现事件源实现可以更好地服务于业务。
对 current-account
的更改是否需要对 air-miles
服务进行匹配更改?如果是这样,我们真的可以说这些服务是相互隔离的吗?
Advantages of taking a decision on local view models
我不是特别喜欢这些"advantages";首先,它们受性能轴支配(请记住性能优化的 第二 规则是 "not yet")。其次,他们假设服务边界是正确绘制的;也许性能问题是职责分离需要审查的证据。
So the problem is this Should the air-miles microservice take decisions based on its own view model which is being updated from events coming from the current-account, and similarly, on picking which reward it should give out to the Customer?
是的。事实上,你应该修改你的架构,甚至创建更多的微服务。我的意思是,作为事件驱动架构(也是事件源架构),您的微服务有两个职责:它们需要保持两种不同的模型:写入模型和读取模型。
因此,每个聚合应该是一个只保留写入模型的微服务,也就是说,它只处理命令,而不会同时构建读取模型。
然后,对于每个 read/query 用例,您应该有一个构建 完美 读取模型的微服务。如果您需要保持聚合微服务清洁(正如您应该做的那样),这是必需的,因为通常,读取模型需要来自多个聚合 types/bounded 上下文的数据。读取模型可能会跨越有界上下文边界,而聚合则不会。所以你看,如果你需要完全尊重 DDD,你真的别无选择。
有人说领域事件应该被隐藏,只对拥有的微服务是本地的。我不同意。在事件驱动的架构中,域事件首先是 class 公民,它们被允许访问其他微服务。这让其他微服务有机会构建自己的系统状态解释。否则,发出微服务将无法 additional responsibility/task 构建一个状态,该状态必须满足所有微服务可能需要的所有可能需求(!);即,也许微服务想要查找 deleted 远程实体的 title
,如果发出微服务只保留 non-deleted 的列表,它怎么能做到这一点-还有个实体?你可能会说:但它会保留所有实体,无论是否删除。但也许有人需要实体被删除的日期;你可能会说:但我也保留了 deletedDate
。你看你做什么?你违反了 Open/closed 原则。每次创建微服务时,您都需要修改发出微服务。
还有微服务的弹性。在 Art of scalability 中,作者谈到了泳道。它们是一种将系统组件划分为故障通道的策略。一个通道中的故障不会传播到其他通道。我们的微服务是通道。一个通道中的组件不允许访问其他通道中的任何组件。一个宕机的微服务不应导致其他微服务宕机。这不是 speed/optimisation 的问题,而是韧性的问题。域事件是保持两个远程系统同步的完美方式。他们还强调数据最终是一致的;事件以有限的速度传播(从纳秒到偶数天)。当一个系统在设计时考虑到了这一点,那么没有其他微服务可以降低它。
是的,会有一些代码重复。是的,虽然我说你别无选择,但你有。为了以较低的弹性为代价减少代码重复,您可以拥有一些构建 正常 平面状态的规范读取模型,而其他微服务可以查询该状态。这在大多数情况下是危险的,因为它打破了泳道的概念。如果 Canonical 微服务宕机,则关闭所有依赖的微服务。 Canonical 微服务最适合类似 CRUD 的限界上下文。
然而,在某些情况下,您可能有一些不想公开的内部事件。换句话说,您不需要发布所有领域事件。
所以我将通过使用示例来解释问题,因为它使一切都更加具体,并希望能减少歧义。
架构非常简单
1 个微服务 <=> 1 个聚合 <=> 事务边界
每个微服务都将使用 CQRS/ES 设计模式,这意味着
- 每个微服务都有自己的聚合映射现实世界问题的领域
- 将从事件存储中重建聚合状态
- 每个事件都表示聚合内的状态更改,并将通过消息代理传输到对更改感兴趣的任何服务
- 每个微服务都将在其自己的域内进行事务处理
- 每个微服务最终都会与其他域保持一致
- 每个微服务都会根据其他微服务发出的事件构建自己的视图模型
所以这个例子假设我们有一个银行系统
current-account
微服务负责映射客户活期账户...取款,存款rewards
微服务将负责对银行提供的任何奖励进行盘点和盘点air-miles
微服务将负责监控来自current-account
的所有交易,并通过我们的奖励微服务 奖励客户
所以问题是 air-miles
微服务是否应该根据自己的视图模型做出决策,该视图模型正在根据来自 current-account
的事件进行更新,同样地,选择它应该奖励的内容送给客户?
在本地视图模型上做出决策的缺点;
- 复制有关如何维护这些视图的域逻辑
- 视图中的错误可能会传播错误的奖励
- 损坏的视图模型上的状态更改(也就是发出的事件)可能会对其他服务产生影响,这些服务正在对这些事件做出自己的决定
在本地视图模型上做出决定的优势;
系统不需要不断查询拥有域的微服务
系统应该更快,资源占用更少
或者它应该使用来自服务的事件来触发对拥有域的聚合的查询,在这样做时我们接受视图模型可能损坏的事实,但最终决定应始终咨询拥有的聚合域名?
拜托,以上问题只是我对架构的理解,这个post的目的是就如何在微服务环境中有效地使用这个架构获得不同的看法,以保持每个服务分离但避免级联损坏场景,服务之间没有太多的喋喋不休。
So the problem is this Should the air-miles micro service take decisions based on its own view model which is being updated from events coming from the current-account, and similarly, on picking which reward it should give out to the Customer?
每个消费者使用生产者计算的表示的本地副本。
因此,如果 air-miles
需要来自 current-account
的信息,它应该查看由 current-account
服务计算的视图的本地副本。
关键思想是:微服务应该相互隔离;您应该能够重新设计和部署一个而不影响其他。
所以试试这个思想实验 - 假设我们有这三个微服务,但都保存当前状态的快照,而不是事件。一切正常,然后想象 current-account
维护者发现事件源实现可以更好地服务于业务。
对 current-account
的更改是否需要对 air-miles
服务进行匹配更改?如果是这样,我们真的可以说这些服务是相互隔离的吗?
Advantages of taking a decision on local view models
我不是特别喜欢这些"advantages";首先,它们受性能轴支配(请记住性能优化的 第二 规则是 "not yet")。其次,他们假设服务边界是正确绘制的;也许性能问题是职责分离需要审查的证据。
So the problem is this Should the air-miles microservice take decisions based on its own view model which is being updated from events coming from the current-account, and similarly, on picking which reward it should give out to the Customer?
是的。事实上,你应该修改你的架构,甚至创建更多的微服务。我的意思是,作为事件驱动架构(也是事件源架构),您的微服务有两个职责:它们需要保持两种不同的模型:写入模型和读取模型。
因此,每个聚合应该是一个只保留写入模型的微服务,也就是说,它只处理命令,而不会同时构建读取模型。
然后,对于每个 read/query 用例,您应该有一个构建 完美 读取模型的微服务。如果您需要保持聚合微服务清洁(正如您应该做的那样),这是必需的,因为通常,读取模型需要来自多个聚合 types/bounded 上下文的数据。读取模型可能会跨越有界上下文边界,而聚合则不会。所以你看,如果你需要完全尊重 DDD,你真的别无选择。
有人说领域事件应该被隐藏,只对拥有的微服务是本地的。我不同意。在事件驱动的架构中,域事件首先是 class 公民,它们被允许访问其他微服务。这让其他微服务有机会构建自己的系统状态解释。否则,发出微服务将无法 additional responsibility/task 构建一个状态,该状态必须满足所有微服务可能需要的所有可能需求(!);即,也许微服务想要查找 deleted 远程实体的 title
,如果发出微服务只保留 non-deleted 的列表,它怎么能做到这一点-还有个实体?你可能会说:但它会保留所有实体,无论是否删除。但也许有人需要实体被删除的日期;你可能会说:但我也保留了 deletedDate
。你看你做什么?你违反了 Open/closed 原则。每次创建微服务时,您都需要修改发出微服务。
还有微服务的弹性。在 Art of scalability 中,作者谈到了泳道。它们是一种将系统组件划分为故障通道的策略。一个通道中的故障不会传播到其他通道。我们的微服务是通道。一个通道中的组件不允许访问其他通道中的任何组件。一个宕机的微服务不应导致其他微服务宕机。这不是 speed/optimisation 的问题,而是韧性的问题。域事件是保持两个远程系统同步的完美方式。他们还强调数据最终是一致的;事件以有限的速度传播(从纳秒到偶数天)。当一个系统在设计时考虑到了这一点,那么没有其他微服务可以降低它。
是的,会有一些代码重复。是的,虽然我说你别无选择,但你有。为了以较低的弹性为代价减少代码重复,您可以拥有一些构建 正常 平面状态的规范读取模型,而其他微服务可以查询该状态。这在大多数情况下是危险的,因为它打破了泳道的概念。如果 Canonical 微服务宕机,则关闭所有依赖的微服务。 Canonical 微服务最适合类似 CRUD 的限界上下文。
然而,在某些情况下,您可能有一些不想公开的内部事件。换句话说,您不需要发布所有领域事件。