DDD过程管理器:它们是否是业务逻辑的一部分? DDD过程管理器是否属于业务逻辑?

9
一些来源声称流程管理器不包含任何业务逻辑。例如,Microsoft文章就这样说:
“您不应该使用流程管理器来实现您域中的任何业务逻辑。业务逻辑属于聚合类型。”
他们还在更上面这样说(重点是我加的):
“值得注意的是,流程管理器不执行任何业务逻辑。它仅路由消息,并在某些情况下翻译消息类型。”
然而,我无法理解为什么消息之间的翻译(例如从域事件到命令)不是业务逻辑的一部分。你需要一个域专家才能知道正确的步骤顺序和它们之间的翻译。在某些情况下,你还需要在步骤之间保持状态,甚至根据一些(业务)条件选择下一个步骤。因此,并非所有事情都是给定步骤的静态列表(尽管我会把这个单独称为业务逻辑)。
在许多方面,流程管理器(或saga)只是另一种聚合类型,可以保持状态并具有一些业务不变量,我个人认为。
假设我们使用六边形架构实现DDD,我将把流程管理器放在应用程序层中(不是适配器!!),这样它就可以对消息做出反应或由计时器触发。它将通过存储库加载相应的流程管理器聚合,并调用其方法,以设置其(业务)状态或询问下一个要发送的命令(当然,实际发送由应用程序层执行)。此聚合位于域层中,因为它执行业务逻辑。
我真的不明白为什么人们区分业务规则和工作流规则。如果删除除领域层以外的所有内容,则应该能够重建一个可工作应用程序,而无需再次咨询领域专家。
我很乐意从你们那里获得我可能错过的进一步见解。

1
你能提供一个例子,展示出你认为这样的进程管理器会包含哪些业务逻辑吗? - Andreas Hütter
从链接的文章中:「例如,当它收到一个SeatsNotReserved事件时,它会发送一个AddToWaitList命令。」仅在给定业务规则的情况下,它才能知道此翻译。 - domin
2个回答

2
这里的混淆很大程度上是语义扩散的结果。 "Process manager" 的拼写来自于《企业集成模式》(Hohpe和Woolf,2003)。在那里,它是一种消息模式;更确切地说,它是消息路由器的一种可能的专业化。消息路由器的动机是发送者和接收者的解耦。如果定义了新的消息类型、添加了新的处理组件或更改了路由规则,我们只需更改消息路由器逻辑,而所有其他组件都不受影响。在这个背景下,流程管理器指的是坐落于枢纽和辐条设计中的消息路由器的一种专业化,它维护处理序列的状态并“根据中间结果确定下一个处理步骤”。当然,“流程定义”是企业关心的事情——毕竟,我们在不同部门之间传递这些消息以协调活动。

是的,这个维护“处理序列状态”的东西听起来很像一个“领域实体”的例子,这是真的。

但是:它是消息路由域的实体;也就是说,它是为了确保消息到达正确位置而进行的簿记,而不是业务信息的簿记(例如:运输集装箱的路线)。

用六边形架构的语言表达,过程管理器所做的是跟踪发送给其他六边形的消息(当然也包括它们发送回来的消息)。


我同意语义扩散的观点。很难找到清晰的定义,所以感谢您指出其来源! - domin
如果工作流管理器只是在六边形(或微服务或部署单元或其他)之间路由消息,那么消息之间的翻译在哪里?毕竟它们必须是某个领域的一部分。还是说工作流从发起者那里接收到一种预定义的协议定义,告诉它该怎么做? - domin
翻译最终在六边形的端口/适配器中完成。 端口/适配器处于六边形之间的无人区:可以说端口/适配器位于一个、两个或两个六边形都不属于。 - Levi Ramsey
@Levi Ramsey:为什么需要翻译?适配器可以将一个接口适配到另一个具有相同功能但形式略有不同的接口,例如从WebAPI接口到C#接口。从事件到命令的转换是核心业务逻辑,因此必须驻留在六边形的核心中。我认为将其放在saga聚合或域服务的形式中没有问题。围绕它的技术编排(超时、重试、日志等)属于应用程序层。 - domin
来自六边形A的事件变成了六边形B中的命令。六边形A改变其事件不应强制更改六边形B的核心,同样地,六边形B改变其命令也不应强制更改六边形A的核心。因此,需要一个适配器将来自A的事件转换为B的命令(当任一方发生更改时,适配器必须进行更改,但更改的影响范围止于适配器)。 - Levi Ramsey
一个事件中的更改是否应该强制引起下游六边形(或DDD术语中的有界上下文(BC))的更改,取决于这些BC之间的关系类型。在BC之间存在某种耦合是完全有效的,就像相应的子域一样。另一方面,对于BC-内部聚合的流程/ saga,我们再次进行了相同的讨论!翻译逻辑应该在领域层中,否则您必须承认业务逻辑泄漏到应用程序甚至适配器层中。 - domin

1

领域逻辑不仅存在于聚合和领域服务中,还存在于其他地方:

  • 适当地处理领域事件。领域事件可以转换为一个或多个命令传递给聚合;它们可以基于事件本身和/或其他聚合的状态触发这些命令;它们可以通知正在进行的业务流程继续执行其下一步等。所有这些都是领域逻辑的一部分。
  • 业务流程是一个(可能分布式的)状态机,可能涉及各种参与者/用户/系统。允许的状态和状态之间的转换都是领域逻辑的核心部分。
  • 传奇是一个最终一致的事务,跨越多个本地或外部聚合完成,要么成功完成,要么以尽力而为的方式补偿已经执行的步骤。组成传奇的步骤只能由领域专家知道,因此它们是领域逻辑的一部分。

我认为这三件事被误认为是应用程序层面的问题的原因如下:

为了处理领域事件,我们必须加载并稍后保存受影响的聚合。因此,处理程序必须同时是应用程序层的一部分。但至关重要的是,不仅如此。如果我们尊重六边形架构背后的思想,那么对于驻留在应用程序层中的每个领域事件处理程序,必须有一个相应的位于领域层中的处理程序。即使是最琐碎的情况,其中一个领域事件恰好转换为某个聚合上的一个命令方法调用。这可能在许多示例中被省略,因为它最初增加了很少的价值。但是请想象一下,稍后的翻译将基于进一步的业务条件。我们是否也会将其放置在应用程序层处理程序中?记住:所有我们的领域逻辑都应该在领域层中。
  • 附注:即使我们尊重这种关注点的分离,我们仍然可以选择让领域事件由聚合本身处理,或者让它通过薄领域服务转换为聚合命令。然而,这个选择基于完全不同的关注点:我们是否想更紧密地耦合聚合。这里没有对错之分。有些事情自然会更紧密地耦合,而其他事情可能会从一些额外的间接性中受益,以增加灵活性。
  • 为了正确实现业务流程或saga,我们必须处理各种应用程序特定的关注点,如消息去重、幂等性、重试、超时、日志记录等。尽管有人可能认为领域逻辑本身应该首先负责处理至少其中一些方面(请参见Vaughn Vernons excellent talk about modelling uncertainty)。但是请记住,序列(允许的)步骤/操作的本质完全基于领域逻辑。

最后,谈一下耦合的问题。在我看来,社区中存在这样一种趋势,即耦合本身是一件坏事,因此必须尽一切可能避免/减轻它。这可能会导致像将事件-命令转换(记住:领域逻辑!)放置在六边形/洋葱/清洁架构的适配器层之外的解决方案。这个层的责任是将某些东西适应到另一些具有相同语义/功能但略有不同形式的东西上(想象一下电源适配器)。即使它非常简单,也不是托管任何类型的领域逻辑的地方。企业有各种各样的依赖和耦合。关键在于在实际存在的地方接受它,并在其他情况下避免它。这就是为什么我们在DDD中有合作伙伴或客户/供应商关系的原因。如果我们关心领域逻辑隔离,那么这些依赖关系就反映在它们所属的领域层中。

  • 一个旁注:反腐层(DDD)是适配器的一个有效示例。例如,它可以获取一堆远程域事件并以任何必要的方式转换/组合它们以适应本地模型。它们仍然是过去发生的事件,并不会神奇地变成命令。转换只改变形式,而不是功能。它也不能消除从领域角度不可避免的耦合。它只是用稍微不同的语言重新表述了同样的事情。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接