【问题标题】:DDD - Using a Process Manager or a Domain ServiceDDD - 使用进程管理器或域服务
【发布时间】:2018-03-27 00:59:34
【问题描述】:

我是 DDD 的新手,我正在我的应用程序的一部分上实现它,因为应用程序的一些要求引导我使用事件源的 CQRS(需要系统中发生的事件的历史记录加上需要能够查看系统过去的状态)。
阅读 Vaughn Vernon 的书及其有效的聚合设计系列后,我有一个问题是流程管理器(长期运行的进程)和域服务之间的区别是什么。特别是当您将导航属性指向另一个聚合时

我会解释我所理解的:
- 域服务用于保存不属于任何聚合的逻辑。根据 Vaughn 的说法,它也可以用于将实体引用传递给包含它的聚合。它也可以用来管理事务,因为它们不能被处理到域对象中
- 流程管理器用于协调对系统进行的修改并跨越不同的聚合。有人说一个好的流程管理器实际上是一个聚合根。据我了解,它不管理事务,因为在提交更改后启动事件。它使用最终一致性的方法。最终所有的变化都会发生

现在,将所有内容放在上下文中。我正在构建的应用程序的核心是处理包含它们自己的逻辑的节点树。我们需要能够将节点添加到树中,当然还需要创建这些节点。
我们需要能够知道这些节点发生了什么。即我们需要能够检索链接到节点的事件
此外,对其中一个叶子所做的修改(取决于修改的类型)将必须复制到作为该节点的父节点的其他节点。

什么是我的聚合:
- 节点,这是我的树包含的内容。在我看来,这是一个汇总有几个原因。它不是不变量,因此不是值对象。他们有自己的领域逻辑,允许他们分配它的属性它的值对象,我们需要能够使用 Ids 访问它们
- 由节点组成的非二叉树的表示。现在,我实际上将它设计为我的聚合根,它实际上是一个流程管理器。该树包含该树的逻辑表示。它包含树的根。这个根实际上是一个对象(我不确定它是否可以命名为值对象,因为它包含对其他聚合子节点的引用,但听起来确实如此)。树中的节点对象包含节点名称等基本信息,以及对实际聚合的引用(这几乎听起来像两个有界上下文?)

使用这种方法正在发生这种情况:
- 执行创建节点的命令后,节点被创建并提交。启动 NodeCreated 事件,被正确的处理程序捕获,该处理程序检索与此节点关联的树(进程管理器)并将节点添加到正确的位置(使用节点的父 id 属性)
- 执行命令修改节点后,节点被修改并提交。 NodeModified 事件被启动,被处理程序捕获。然后,处理程序检索树(我的流程管理器)并找到修改节点的所有父节点,并要求这些节点根据子节点上的修改来修改自己的属性。这一切都很有道理,在我看来几乎是美丽的,展示了事件的力量和领域逻辑的分离

但是,我的主要问题是交易。如果在更新树和必须修改或添加的节点时发生错误,会发生什么?节点的事件已保存在事件存储中,因为它已提交。所以我必须创建一个新事件来恢复修改?我知道命令在进入系统时必须是有效的,所以它不会是一个验证问题,并且发生某些事情的可能性是百万分之一。这是否意味着我们不应该考虑这种可能性?

交易问题是我觉得我应该使用服务的原因。一个应用程序服务(这里是一个命令处理程序)或一个域服务来编排动作并在单个事务中执行它们。如果在此事务中发生故障,则不会创建/修改任何内容,但这违反了 DDD 的规则,即我不应该在同一个事务中修改多个聚合。这在某种程度上看起来是一个不太优雅的解决方案

我真的觉得我在这里遗漏了一些东西,但我不太确定它是什么。

【问题讨论】:

    标签: domain-driven-design


    【解决方案1】:

    有人说一个好的流程管理器实际上是一个聚合根

    在我看来这是不正确的。流程管理器或 Saga 协调跨越多个聚合实例的长期运行的业务流程。它最终使系统处于有效的最终状态。它不发出事件,而是响应事件并创建到达聚合的命令(可能通过命令处理程序,具体取决于您的确切架构)。那些说未能正确识别他们聚合边界的架构师。

    流程管理器/Saga 可以是有状态的——但只是为了记住它已经取得的进展;它可以有一个进程 ID;它甚至可以是事件来源的。

    流程管理器用于协调对系统进行的修改并跨越不同的聚合。

    是的,这是正确的。

    执行修改节点的命令后,节点被修改并提交。

    在设计聚合时,您必须只考虑对不变量的保护,以及架构的写入/命令端存在的业务规则;这是产生状态转换的一侧,在事件驱动架构的情况下发出事件。

    我在您的具体案例中确定的单一业务规则(如果有)是,当一个节点创建时(看起来像一个 CRUD 操作!)NodeCreated 事件被发出;类似于NodeModified。所以,这些操作存在于写/命令端。

    NodeModified 事件被启动,被处理程序捕获。处理程序然后检索树(我的流程管理器)并找到修改节点的所有父节点,并要求这些节点根据子节点上的修改来修改自己的属性

    关于父节点的更新,写入端是否有任何业务规则?我没有看到。当然,在创建节点后会更新某些内容,但它不是聚合模型而是读取模型。您被调用的 Handler 实际上是一个 Read 模型。它将 NodeXXX 事件投影到节点树上。

    【讨论】:

    • 当您询问是否有一些关于父节点更新的业务规则时,我现在唯一拥有的就是它必须考虑到它自己的状态。含义取决于更新的值,值会相应地更改,依此类推。父节点的父节点将使用其自己子节点的值进行更新。但是我不明白如果我不记录那些事件,如果我只将修改保存在读取状态,我该如何重建我的树的状态和过去的节点?
    • @Guigui 是的,但是父节点在处理未来命令时是否使用这些新值(存储在父节点上)?还是它们只是用于向用户显示?
    • 抱歉误解了这个问题。是的,之后它们将不得不用于运行一些逻辑。例如返回一个子树,其节点只包含给定值
    • @Guigui 但这是查询/读取类型的逻辑,而不是聚合使用的写入/命令类型的逻辑。
    • @Guigui 让我换个方式问你:聚合(即节点)是否使用此信息来拒绝或接受命令?
    【解决方案2】:

    我真的觉得我在这里遗漏了一些东西,但我不太确定它是什么。

    您的域模型可能过于复杂。

    域服务通常是服务提供者,它使域模型能够访问(缓存)状态或它通常不具有的功能。例如,我们可以使用域服务让模型访问缓存的税表,以便它可以计算订单的税;或者我们可以使用域服务让模型访问委托给电子邮件基础架构的 notifyCustomer 功能。

    流程管理器通常用于编排 - 它们基本上是状态机,可以查看发生了什么(事件)并建议运行其他命令。见Rinat Abdullin's description

    如果在更新树和必须修改或添加的节点时发生错误,会发生什么?节点的事件已保存在事件存储中,因为它已提交。所以我必须创建一个新事件来恢复修改?

    也许 - 补偿事件是一种常见模式。

    要点是:跨多个事务编排更改并没有什么魔力。想一想您将如何安排一个 UI,向操作员显示正在发生的事情、接下来会发生什么以及故障模式是什么。

    发生某事的几率是百万分之一。这是否意味着我们不应该考虑这种可能性?

    取决于对业务的风险。但正如 Greg Young 在他的演讲 Stop Over Engineering 中指出的那样,如果您可以将百万分之一的问题上报给人类来解决,那么您可能已经做得足够了。

    【讨论】:

      猜你喜欢
      • 2016-11-15
      • 1970-01-01
      • 2015-10-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-08
      • 1970-01-01
      相关资源
      最近更新 更多