比方说,我有一个域类,它具有要按顺序调用的功能。每个函数都执行其工作,但如果序列中的上一步尚未完成,则会引发错误。另一种方法是每个功能完成其运行所需的步骤,然后执行自己的逻辑。我觉得这种方式不是一个好习惯,因为我要添加多个职责,并且调用方在调用方法时将不知道所有操作都会发生什么。
我的问题是,如何处理DDD中的依赖方案。调用者有责任按正确的顺序调用方法吗?还是我们让这些方法先处理相关的操作,然后再处理其自身的逻辑?
答案 0 :(得分:0)
调用者有责任按正确的顺序调用方法吗?
可以确定这些方法是否具有商业意义。例如,客户可以预订航班,然后预订酒店房间。两者都是客户理解的,按此顺序调用它们是客户的逻辑。另一方面,将保留插入数据库中,然后提交(或执行任何其他操作)是技术性的。客户根本不必处理这个问题。或“初始化”一个对象,然后调用其他方法,然后调用“ close”。
要求一系列技术调用是时间耦合的一种形式,被认为是一种不好的做法,与DDD没有直接关系。
解决方案是对问题进行更好的建模。调用者希望通过此调用序列实现更高级别的用例。因此,与其发布所需的单个“步骤”,不如从整体上支持更高的用例。
通常,您应该始终以目标为设计目标,以获取有效调用序列,以实际表示(在语言允许的范围内)。
更新:上述“文件”域的可能模型:
public interface LocalFile {
RemoteFile upload();
}
public interface RemoteFile {
RemoteFile convert(...);
LocalFile download();
}
答案 1 :(得分:0)
从我的角度来看,您所描述的是领域模型操作的编排。这就是应用程序层(基于域模型的层)的工作。您应该具有一个应用程序服务,该服务将按正确的顺序调用域模型方法,并且还应考虑到某个步骤是否遗漏了任何任务,在这种情况下,请告知下一步执行该任务。
答案 2 :(得分:0)
TLDR;滚动到底部寻找答案,但背景故事会提供一些良好的上下文。
如果进入您域的呼叫者必须知道呼叫的顺序,那么您就错过了将业务逻辑封装在域中的机会,这是anemic domain的症状。
@RobertBräutigam指出了一个很好的观点:
要求一系列技术调用是时间耦合的一种形式,被认为是一种不好的做法,与DDD没有直接关系。
这是正确的,但是当您使用域模型进行操作时,这是更糟,因为非域问题与域问题混在一起。在非业务逻辑的海洋中,意图迷失了。如果可以的话,您会寻找封装顺序的高阶aggregate。借用罗伯特(Robert)的例子,而不是先预定机票然后预定酒店房间,然后强迫客户使用,可以将假期汇总并验证。
我知道您的情况听起来错误,我怀疑您是对的。有一个明显的依赖性,即不能同时发生,所以我们不能成为故事的结局。当您对必须在“最终”状态之前发生的中间事务有明确的依赖性时,我们就可以进行编排(请考虑使用sagas,分布式事务,域事件和所有这些好处)。
您对文件操作的描述涵盖了整个事务。域的操作(状态更改)在分布式事务中的每个点都是事务性的,但不是整体事务性的。所以当@ choquero70说
您所描述的是领域模型操作的编排。这就是应用程序层,即域模型层的工作。
那也是正确的。编排是关键。每个步骤都必须操作一次域状态,并且只能操作一次,并将其置于有效状态,但是可以有多个步骤。
时间轴上的每个单独点都是您所在域状态下的有效时刻。
因此,回到您的模型。如果您公开了一个接口,并且可能对 all 步骤进行了多次调用,那么您将无所事事。使它不可能或至少不可能。编排不仅是要做什么,而且是要防止发生什么。创建较小的接口/类,以避免意外增加可能被误用的内容的“表面积”。
通过这种方式,您可以通过向调用者提供有效的中间状态来指导他们下一步该做什么。但是,这是重要的部分,按呼叫顺序的呼叫负担不是呼叫者上的。当然,呼叫者可以知道该怎么办,但是为什么要强制这样做。
您的基本算法是相同的:上传,转换,下载。
调用者有责任按正确的顺序调用方法吗?
不完全是。给定您域的状态,呼叫者有责任从合法选择中进行选择。通过业务方法将这些选择呈现在正确建模的moment/interval聚合上,以适合调用者使用是您的责任。
还是让方法在逻辑依赖之前处理依赖的操作?
如果正确设置了业务流程,则不需要这样做。但是无论如何验证还是有道理的。
顺便提一句,业务流程的每个步骤本质上都应该是线性的。我告诉开发人员对其中包含 if 语句的编排步骤感到怀疑。如果存在 if ,则最好将其作为另一个编排步骤的一部分或封装在业务逻辑中。