一个领域模型有多松散耦合?

3
问题:我应该将依赖关系反转,让DomainModel依赖于持有系统接口和高级策略的Core吗?

我们是否会将丰富的领域模型视为实现细节?

背景:所以我的DomainModel在一个独立的项目中,没有向外部的依赖。我的所有子系统服务都在单独的项目中,它们互相不知道。我还有一个Core项目,其中包含所有特定于系统的内容,如接口、枚举、验证类、扩展和其他所有服务都不被归类为低级别实现细节的东西。Core除了对DomainModel的依赖之外没有向外部的依赖,所有的服务都依赖于Core,并且也依赖于DomainModel我的当前想法:我听说过设计语句,比如:

“领域模型不应该有向外部的依赖”

潜在地相对的是

“高级策略不应依赖于低级别实现细节”

“模块之间的依赖方向应该是稳定性的方向”

我发现在开发系统时,域模型的实现经常会有很大的变化,所有业务规则和对象都存在,但我不认为大多数域模型都是稳定的,可能只有一些像 ValueObject 这样的模型是稳定的。

我认为将 DomainModel 依赖于 Core 的好处在于高级策略不需要了解域模型的实现细节,同时域模型也与系统的其余部分隔离开来。事实上,每个其他项目都将仅依赖于 Core并且仍然不知道彼此。当我更改 DomainModel(我经常这样做)时,构建将重新编译该项目,而不是像目前这样重新编译整个解决方案。

我认为在实现这种额外的松耦合度时,缺点是增加了复杂性?我需要为每个单独的领域模型对象创建接口,这些接口将位于Core中的一个特殊文件夹中。对于这些接口,我还想删除“ I”前缀,并使用MoneyType:Money [interface]。我想我唯一能实例化领域对象的方式是通过抽象工厂,所以我还需要更多的抽象工厂。现在,我确实喜欢能够在系统的“任何地方”实例化具体的领域对象,几乎像BCL的扩展或抽象(这也是域模型的一部分吧?)。
我可以想象系统使用任何一种架构都可以工作,那么这只是竞争设计问题的经典案例,还是我完全错过了什么?
编辑:这个高票答案似乎表明领域模型应该被隔离,每个对象只向系统的其余部分公开一个接口。

https://dev59.com/FnRA5IYBdhLWcg3wzhRY#821300

目前来看,这对我来说感觉更好,因为系统的其余部分没有无限制地访问域对象(因此每次更改都需要重新编译它们)。然而,将这种依赖关系完全反转以实现完全隔离并不是一项微不足道的任务,因此我非常乐意进行更多的讨论和回答。

更多编辑:因此,我尝试了重构,在我开始尝试实现自己的简单DI容器时,我决定事情开始变得有点臭味相投和有些荒谬。

完全隔离DomainModel的任何感知优势都被指数级增加的复杂性和代码行数所抵消(至少对我来说是这样)。因此,我将架构重构为DomainModel仅依赖于Core,后者现在仅包含我使用的按设计约定的类、自定义集合、注释和常见扩展。我将所有特定于系统的接口和基础设施移到一个新项目Infrastructure中,所有子系统服务都依赖于此(这里没有特定的技术,因此可能不是典型的基础设施层?)。

现在一切都感觉好了,但我又回到了依赖于具体的领域对象上,这对我来说没问题。这并不是一次徒劳的练习,因为我发现了几个改进和简化领域模型的方法,并且能够澄清核心并确定基础设施的机会。

你是指洋葱架构中的“核心”吗? - Constantin Galbenu
@ConstantinGALBENU 我其实不熟悉那个框架,Core只是我这个项目的名称。 - Christopher Sellers
2个回答

4
Onion architecture中,它是与DDD很好地相结合的一种架构,Domain层可以进一步分为两个子层:
  1. Core:包含不特定于任何领域或技术的构建块,包括通用构建块,如列表、案例类和演员。它永远不会包括技术概念,例如REST或数据库。
  2. Domain:实际的领域层,所有业务逻辑都驻留在其中,使用通用语言命名的类和方法。通过通过API控制领域并将所有业务逻辑放入领域中,应用程序变得可移植,所有技术细节都可以提取而不会丢失任何业务逻辑。
因此,您可以有一个Core组件,该组件在有界上下文之间共享,但我认为这不一定要在单独的项目中。这取决于您希望如何将更改传播到其他项目。
关于依赖项,Domain sub-layer将依赖于Core sub-layer,因为它使用其低级别类,但这不会违反依赖反转原则,因为两者在同一层中。然而,整个Domain layer不应该依赖于任何其他层,如ApplicationPresentationInfrastructureDomain layer应该保持纯净,没有副作用和依赖关系。这一规则应该在任何架构中得到尊重。

是的,但请注意这两个子层来自领域层,它们遵循领域层规则:纯净、无副作用、技术无关、无IO、无外部调用等。 - Constantin Galbenu
是的,完全正确。这让我想起了我读到的关于“功能域模型”的内容。我的所有值对象都是不可变的,事实上,我努力使一切都尽可能不可变和只读。 - Christopher Sellers
但你不必使所有东西都是不可变的。不,不是这样的。只有值对象。实体应该是可变的,但它们不会自己持久化。一个不能持久化自身的可变对象就像一个不可变对象:它没有副作用;“不可变性”不是领域层DDD要求,“无副作用”才是。 - Constantin Galbenu
我在我的答案链接中发现了这个Core子层,并且我也发现在我的项目中,这种子层可能会出现。例如,我有一个GuidList作为GUID列表;行为的例子是GUID列表的交集、并集和比较。在PHP中,我们没有这种类型的类,所以我们必须自己实现它们。我认为,无论是PHP还是C#都不能提供领域层需要的所有语言结构,以便完全实现。 - Constantin Galbenu
@guillaume31 是的,这就是为什么我在你最后一条评论之前发表了评论。 - Constantin Galbenu
显示剩余5条评论

1
我还有一个核心项目,其中包含所有系统特定的内容,比如接口、枚举、验证类、扩展和其他所有服务共用的内容。
您正在描述与特定层无关的代码结构和模式。
接口和枚举,只要它们是关于域概念的,应该放在域层。其他接口和枚举...好吧,放在最合适的地方。
"验证类"也非常模糊,但是域验证应该放在域中,命令验证应该放在应用程序层中,用户输入验证应该放在UI中等等。
扩展方法可以在任何层中找到。
我认为您应该摆脱那个看起来不太连贯的“核心”项目,并将其所有内容分散到适当的项目中。
在我看来,“高级策略”的所有内容都应该放在域中。由所有项目共享的实用程序类可以放在类似库的项目或NuGet包中,但这些类不应该太多,并且我也不会称其为核心。

核心项目确实存在一些不连贯的问题,其中的某些类肯定可以分发出去,但是有一些类被所有项目使用,而且我发现将所有接口都放在那里可以很容易地保持所有子系统项目的独立性... 我不确定将接口放入领域模型项目中是否是解决方案,因为那就是我开始的地方... - Christopher Sellers
我不确定将接口放入域模型项目中是否是解决方案 - 这就是原始的洋葱架构方法所建议的(我的意思是,对于域接口)。有些域接口甚至不在域的中心,而是稍微靠边缘:围绕域模型的第一层通常是我们会找到提供对象保存和检索行为的接口 - guillaume31
“我不确定将接口放入领域模型项目中是否是解决方案” - 好吧,至少它解决了你的依赖问题,对吧? - guillaume31
1
我同意那似乎不是正确的解决方案。我不会称它为我所遇到的“问题”,更像是对系统质量最优化的追求——就像软件开发中的许多事情一样,它可能只是在竞争关注点之间做出妥协的结果… - Christopher Sellers

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