CQRS:写入模型可以使用读取模型吗?
CQRS: Can the write model consume a read model?
读CQRS时经常提到写模型不应该依赖于任何读模型(假设有一个写模型和最多N个读模型)。这很有意义,特别是因为读取模型通常只会最终与写入模型保持一致。此外,我们应该能够在不破坏写入模型的情况下更改或替换读取模型。
但是,读取模型可能包含有价值的信息,这些信息是跨写入模型的许多实体聚合的。这些聚合甚至可能包含重要的业务规则。人们可以很容易地想象一个 业务策略 评估读取模型拥有的一条信息,并通过写入模型更改一个或多个实体作为响应。但是这个策略应该放在什么地方located/implemented?这不是将来自特定读取模型的信息与写入模型紧密耦合的关键业务逻辑吗?
当我想在不将写入模型耦合到读取模型的情况下实施上述策略时,我可以想象以下策略:在要更新的写入模型中包含一个 物化视图每当相关实体的相关部分发生变化时同步(使用 DDD 时,这可以通过域事件完成)。但是,这会使写入模型非规范化,并且实际上是一种嵌入写入模型本身的特殊读取模型。
我可以想象 DDD 纯粹主义者会说这样的政策不应该存在,因为它代表了一个包含多个实体(a.k.a. 聚合体)的业务 invariant/rule。理论上我可能会同意,但在实践中,反正我经常遇到这样的需求。
最后,我的问题很简单:您如何处理更改数据以响应某些评估需要读取模型的条件的需求?
首先,任何验证命令的写入模型都是读取模型(因为在某些时候验证命令需要读取),尽管它是为了验证命令而优化的。所以我不确定您在哪里看到写入模型不应依赖于读取模型。
其次,域事件隐含地向事件的消费者发出命令:“process/consider/incorporate 此事件”,在这种情况下,写入模型处理器可以订阅由不同写入模型产生的事件:从订阅写入模型的角度来看,这些只是命令。
阅读了很多有关该主题的文章并亲自认真思考后,我尝试回答我自己的问题。
首先,澄清一下所用的术语。写入和读取模型 自身 从不相互依赖。相应的命令和查询组件可能有。因此,我将整个命令组件及其写入模型称为 命令端 ,并将一个特定查询组件及其读取模型的整体称为 查询端 (其中可能有很多)。
因此考虑一个负责评估和执行业务策略的命令处理程序。它接受一个命令 DTO,对其进行验证,将部分写入模型加载到内存中,并在一个原子事务中对其应用更改。具体的问题是,是否允许此处理程序查询查询端之一,以便告知其关于在写入模型中执行的操作的决定。
答案将是一个响亮的否。原因如下:
- 命令端将依赖于一个特定的查询端(如果将依赖项隐藏在接口后面并不重要——它仍然存在),因此查询端不能独立更改。
- 谁真正保证命令处理程序在必要时运行?查询端当然不是负责它的一方,客户端也不是。
- 执行嵌套查询请求会延长命令请求时间,这可能会影响性能。
相反,我们可以执行以下操作:
- 处理写入模型引发的域事件,在评估策略的命令端注册一个域事件处理程序。这样可以保证在需要时执行策略。
- 如果性能允许,此域事件处理程序可以简单地加载评估业务状况所需的尽可能多的写入模型。不要过早优化——也许实体很小,可以很容易地加载到内存中。
- 如果性能不允许,非规范化 写入模型并使用域事件维护所需的统计信息。没有人说写入模型本身不能包含面向查询的数据。作为一个写入模型,简单地说它是一个设计用于写入的模型,这必然也必须包括一些读取方式。
- 最后,如果应用策略不是域逻辑本身的组成部分,而只是一个用例,请考虑将调用它的责任交给客户端或另一个微服务,首先查询我们的查询端之一,然后使用适当的参数调用我们的命令端是完全可以的。
读CQRS时经常提到写模型不应该依赖于任何读模型(假设有一个写模型和最多N个读模型)。这很有意义,特别是因为读取模型通常只会最终与写入模型保持一致。此外,我们应该能够在不破坏写入模型的情况下更改或替换读取模型。
但是,读取模型可能包含有价值的信息,这些信息是跨写入模型的许多实体聚合的。这些聚合甚至可能包含重要的业务规则。人们可以很容易地想象一个 业务策略 评估读取模型拥有的一条信息,并通过写入模型更改一个或多个实体作为响应。但是这个策略应该放在什么地方located/implemented?这不是将来自特定读取模型的信息与写入模型紧密耦合的关键业务逻辑吗?
当我想在不将写入模型耦合到读取模型的情况下实施上述策略时,我可以想象以下策略:在要更新的写入模型中包含一个 物化视图每当相关实体的相关部分发生变化时同步(使用 DDD 时,这可以通过域事件完成)。但是,这会使写入模型非规范化,并且实际上是一种嵌入写入模型本身的特殊读取模型。
我可以想象 DDD 纯粹主义者会说这样的政策不应该存在,因为它代表了一个包含多个实体(a.k.a. 聚合体)的业务 invariant/rule。理论上我可能会同意,但在实践中,反正我经常遇到这样的需求。
最后,我的问题很简单:您如何处理更改数据以响应某些评估需要读取模型的条件的需求?
首先,任何验证命令的写入模型都是读取模型(因为在某些时候验证命令需要读取),尽管它是为了验证命令而优化的。所以我不确定您在哪里看到写入模型不应依赖于读取模型。
其次,域事件隐含地向事件的消费者发出命令:“process/consider/incorporate 此事件”,在这种情况下,写入模型处理器可以订阅由不同写入模型产生的事件:从订阅写入模型的角度来看,这些只是命令。
阅读了很多有关该主题的文章并亲自认真思考后,我尝试回答我自己的问题。
首先,澄清一下所用的术语。写入和读取模型 自身 从不相互依赖。相应的命令和查询组件可能有。因此,我将整个命令组件及其写入模型称为 命令端 ,并将一个特定查询组件及其读取模型的整体称为 查询端 (其中可能有很多)。
因此考虑一个负责评估和执行业务策略的命令处理程序。它接受一个命令 DTO,对其进行验证,将部分写入模型加载到内存中,并在一个原子事务中对其应用更改。具体的问题是,是否允许此处理程序查询查询端之一,以便告知其关于在写入模型中执行的操作的决定。
答案将是一个响亮的否。原因如下:
- 命令端将依赖于一个特定的查询端(如果将依赖项隐藏在接口后面并不重要——它仍然存在),因此查询端不能独立更改。
- 谁真正保证命令处理程序在必要时运行?查询端当然不是负责它的一方,客户端也不是。
- 执行嵌套查询请求会延长命令请求时间,这可能会影响性能。
相反,我们可以执行以下操作:
- 处理写入模型引发的域事件,在评估策略的命令端注册一个域事件处理程序。这样可以保证在需要时执行策略。
- 如果性能允许,此域事件处理程序可以简单地加载评估业务状况所需的尽可能多的写入模型。不要过早优化——也许实体很小,可以很容易地加载到内存中。
- 如果性能不允许,非规范化 写入模型并使用域事件维护所需的统计信息。没有人说写入模型本身不能包含面向查询的数据。作为一个写入模型,简单地说它是一个设计用于写入的模型,这必然也必须包括一些读取方式。
- 最后,如果应用策略不是域逻辑本身的组成部分,而只是一个用例,请考虑将调用它的责任交给客户端或另一个微服务,首先查询我们的查询端之一,然后使用适当的参数调用我们的命令端是完全可以的。