【问题标题】:How to handle dependent behavior in a domain class?如何处理域类中的依赖行为?
【发布时间】:2019-03-11 07:15:54
【问题描述】:

假设我有一个域类,它具有要按顺序调用的函数。每个函数都完成其工作,但如果序列中的上一步尚未完成,则会引发错误。另一种方式是每个函数完成它运行所需的步骤,然后执行自己的逻辑。我觉得这种方式不是一个好的做法,因为我添加了多个职责,而调用者在调用一个方法时不会知道所有操作会发生什么。

我的问题是,如何处理 DDD 中的依赖场景。调用者有责任以正确的顺序调用方法吗?或者我们是否让方法在它自己的逻辑之前处理依赖的操作?

【问题讨论】:

  • 也许您可以提供更具体的场景来帮助我们深入研究您的问题。
  • 嘿,我的用例是关于文件操作的。我的域是 File 类,方法是上传、转换、下载。现在,没有上传就无法进行转换,而imo,在转换中调用上传并不是最佳实践。这就是我需要的建议。我认为这并不特定于我的用例:)

标签: algorithm oop design-patterns domain-driven-design


【解决方案1】:

调用者有责任以正确的顺序调用方法吗?

如果这些方法有商业意义也没关系。例如,客户可能预订航班,然后预订酒店房间。这两者都是客户端理解的,按顺序调用它们是客户端的逻辑。另一方面,将保留插入数据库,然后提交(或其他)是技术性的。客户根本不必处理这个问题。或者“初始化”一个对象,然后调用其他方法,然后调用“close”。

要求一系列技术调用是时间耦合的一种形式,它被认为是一种不好的做法,并且与 DDD 没有直接关系。

解决方案是更好地建模问题。调用者可能希望通过此调用序列实现更高级别的用例。因此,与其发布所需的单个“步骤”,不如将其作为一个整体支持更高的用例。

一般来说,您的设计目标应该是让任何有效调用序列真正表示某些东西(只要语言允许)。

更新:上述“文件”域的可能模型:

public interface LocalFile {
    RemoteFile upload();
}

public interface RemoteFile {
    RemoteFile convert(...);

    LocalFile download();
}

【讨论】:

  • 嗨,罗伯特,感谢您的回复。我明白你在说什么。我不确定这是否可以更好地建模。我的用例是围绕文件操作。我的域是 File 类,方法是上传、转换、下载。现在,没有上传就无法进行转换,而imo,在转换中调用上传并不是最佳实践。你建议如何更好地建模?此外,在这种情况下,客户将只有我。这不是暴露的东西,这一层的所有调用者都会知道顺序。
  • @lazyloader 请参阅上面的更新。如您所见,此域中的任何调用组合都会产生一些有意义的结果,尽管可能只是理论上的结果。
【解决方案2】:

在我看来,您所描述的是域模型操作的编排。那是应用层的工作,是领域模型的一层。您应该有一个应用程序服务,它会以正确的顺序调用域模型方法,并且它还应该考虑某个步骤是否未完成任何任务,在这种情况下,告诉下一步执行它。

【讨论】:

    【解决方案3】:

    TLDR;滚动到底部寻找答案,但背景故事会提供一些很好的背景。

    如果您域的调用者必须知道调用事物的顺序,那么您就错过了在您的域中封装业务逻辑的机会,这是anemic domain 的症状。

    @RobertBräutigam 提出了一个非常好的观点:

    要求一系列技术调用是一种时间耦合形式,它被认为是一种不好的做法,并且与 DDD 没有直接关系。

    确实如此,但是当您使用域模型执行此操作时,情况会更糟,因为非域关注点与域关注点混合在一起。意图在非业务逻辑的海洋中迷失了。如果可以的话,您可以寻找一个封装了排序的高阶aggregate。借用 Robert 的例子,与其先预订航班,再预订酒店房间,然后将其强加给客户端,您可以让 Vacation 聚合同时接受并验证它。

    我知道在你的情况下这听起来错误,我怀疑你是对的。有一个明确的依赖关系,不可能同时发生,所以我们不能成为故事的结局。当您对必须在“最终”状态之前发生的中间事务有明确的依赖关系时,我们就有……编排(想想 saga、分布式事务、域事件和所有这些优点)。

    您对文件操作的描述跨越了事务。域的操作(状态更改)在分布式事务中的每个点都是事务性的,但总体上不是事务性的。所以当@choquero70 说

    您所描述的是域模型操作的编排。这就是应用层的工作,是领域模型之上的一层。

    这也是正确的。编排是关键。每个步骤都必须操作域的状态一次,并且只操作一次,并使其处于有效状态,但可以有多个步骤。

    时间轴上的每个单独点都是您的域状态中的有效时刻

    那么,回到你的模型。如果您公开一个接口,其中包含对 all 步骤的多个可能调用,那么您会让自己对被乱序调用的事情保持开放态度。让这不可能或至少不可能。编排不仅仅是要做什么,而是要防止发生什么。创建更小的接口/类以避免意外增加可能被误用的“表面积”。

    通过这种方式,您可以通过向调用者提供有效的中间状态来指导调用者下一步该做什么。但是,这是重要的部分,以什么顺序调用什么的负担在调用者身上。当然,调用者可以知道该做什么,但为什么要强迫它。

    你的基本算法是一样的:上传、转换、下载。

    调用者有责任以正确的顺序调用方法吗?

    不完全是。调用者有责任根据您的域的状态从合法选项中进行选择。通过业务方法在适合调用者使用的正确建模的 moment/interval 聚合上呈现这些选择是“您的”责任。

    或者我们是否让方法在它自己的逻辑之前处理依赖操作?

    如果您已正确设置编排,则无需执行此操作。但无论如何验证确实有意义。

    附带说明,您所做的编排的每个步骤本质上都应该是非常线性的。我告诉我的开发人员要怀疑其中包含 if 语句的编排步骤。如果存在 if,则最好成为另一个编排步骤的一部分或封装在业务逻辑中。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-07
      相关资源
      最近更新 更多