【问题标题】:Should domain objects have dependencies injected into them?域对象是否应该将依赖项注入其中?
【发布时间】:2013-11-30 21:30:39
【问题描述】:

我特指这个问题: DDD - How to implement factories

所选答案已说明:

“工厂不应与依赖注入绑定,因为域对象不应将依赖注入其中。”

我的问题是:无法将依赖项注入实体的原因是什么?还是我只是误解了声明?有人可以澄清一下吗?

【问题讨论】:

  • 不同意“工厂不应该与依赖注入绑定,因为域对象不应该将依赖注入到它们中”的说法。聚合、实体和值对象不应与依赖注入绑定。但是工厂和存储库可以。
  • 您是在询问在工厂或域实体中注入 deps 吗?
  • 根据声明,我假设“域对象”是域层中的任何构建块(即实体、工厂、存储库..)。我可能是错的。我的问题专门与实体有关,但我更关心的是 TRUE 还是 FALSE。为什么?

标签: dependency-injection domain-driven-design


【解决方案1】:

域对象不是工厂、存储库等。它们只是实体、值对象、域服务和聚合根。也就是说,它们必须是封装您的业务域使用的数据、它们之间的关系以及域可以对该数据执行的行为(读取修改)的类。

存储库是一种抽象出您正在使用的持久性基础架构的模式。它在 DDD 中,因为它使您的应用程序与数据库分离,但并非所有 DDD 应用程序都需要甚至应该使用存储库。

工厂是一种隔离对象构造逻辑的模式。这也是 DDD 推荐的一种好做法,但并非在所有场景中都真正需要。

域对象不应依赖于其他任何东西,因为它们是您应用的核心。一切都将取决于他们。因此,使它们不受其他依赖项的影响,可以明确单向依赖链,并减少依赖图。它们是不变量、模型、基础。改变它们,你可能需要改变很多东西。所以改变其他东西不应该强迫他们改变。

【讨论】:

  • 域对象可能是代表域实体的对象,但将它们视为愚蠢的值对象,没有适当的行为,仅将行为提取到单独的服务(据我了解您的陈述)不是 DDD。对我来说,DDD 就像分解一个人:你有一个名为“Human”的聚合根,其中包含“Body”、“LeftArm”、“RightArm”、“LeftLeg”、“RightLeg”和一个“Head”,它们本身可以有行为( Left/RightLeg.moveForward(), Left/RightHand.makeAFist()) 然后可以通过将行为委托给某些肌肉来实现。
  • @MatthiasHryniszak 域对象是实体、值对象、域服务和聚合根的组合。您应该仔细阅读这些内容的具体含义。但是它们一起封装了您所有的业务数据,以及需要修改您的业务数据的所有业务行为。应该对它们进行建模,以尽可能地映射到它们所建模的业务领域共有的真实姓名、动词和关系。对不起,如果我不够清楚。
  • @MatthiasHryniszak 你的行为在哪里被捕获并不重要。您可以将行为作为方法放在实体、值对象、聚合根和域服务上。虽然只有聚合根和域服务可以用作域模型外部的接口 API。所以在你的例子中,我只能得到一个人类,我必须做 Human.moveLeftArmForward();但现在你意识到,在商业领域,从来没有人要求你这样做,而是说让他挥手。所以你意识到Aggregate应该是Human.wave(),它内部可以调用arm.move
  • @MatthiasHryniszak 如果做 OOP,一个好的建模方法可能是将位置作为值对象。位置有一个 moveUp、Left、Right 等方法,它返回一个新的不可变位置。 Arm是一个实体,它有一个名字、一个位置和一个模型。人是聚合,其实体是根。它暴露了波浪。它有两条胳膊、两条腿和一个身体,还有一个名字。它有人体原理图的数据,所以它知道如何修改手臂的位置,使它看起来像挥动的动画,并且它还保证手臂始终保持与身体的连接,并且它不会奇怪地捆绑.
  • @MatthiasHryniszak 我刚刚注意到您也对我的答案投了反对票,但是如果您仔细阅读它,我想您会发现您没有正确理解它。如果您希望我澄清其中的某个特定部分,请提及,我会尽量使其更清楚。
【解决方案2】:

有点老了,但我真的很想解决这个问题,因为我经常遇到这个问题,并对此发表我的看法:

我经常听到域对象不应该“依赖”事物。这是真的。我经常看到人们推断这意味着我们不应该将东西注入到域对象中。

这与依赖的含义相反。域不应该依赖于其他项目,这是真的。但是,域可以定义自己的接口,然后其他项目可以实现这些接口,然后可以将其注入回域中。众所周知,这就是所谓的依赖倒置(DI)。

字面意思与依赖相反。不允许 DI 进入域完全限制了您准确建模域的能力,强制违反 SRP,并且几乎扼杀了域服务的可用性。

我真的觉得我一定是这里的疯子,因为我觉得每个人都读过“域不能有任何依赖关系”然后认为“被注入某些东西意味着你依赖它”并得出结论“因此我们不能将依赖项注入到域中。

这给我们留下了奇妙的逻辑:

依赖倒置 == 依赖

【讨论】:

  • a) 不是“依赖倒置”而是“控制倒置”,b) 您描述的是插件架构,c) 我同意域可以定义其他人可以定义的扩展点这一事实用于丰富域(这是插件架构的用途)并且 DI 应该能够帮助它)以及域应该能够解耦自身的各个部分并使用 DI 将它们重新组合在一起的事实根据需要。
  • 我认为您在回答中将“域对象”与“域服务”混淆了。这里的“领域对象”是指实体(或模型),在领域驱动设计中,它们不应注入依赖项。
【解决方案3】:

域对象不应该有很多依赖。

根据 Fowler 的 Tell-Don't-Ask 原则 (https://martinfowler.com/bliki/TellDontAsk.html),您会希望域对象尽可能多地完成任务。包括有依赖。但是在 Clean Code (Uncle Bob) 第 6 章中,它提到让数据结构由过程/函数类(服务)操作可能是一个很好的设计。只要您没有结合简单 getter/setter 以及更复杂的告诉-不问操作的混合对象。

Fowler 不同意瘦模型并将其称为反模式 - AnemicDomainModel。 https://www.martinfowler.com/bliki/AnemicDomainModel.html

我不同意福勒的观点。我非常同意另一篇关于这个 Fat-Models 问题的文章中的以下引用:“按照这个逻辑,基本上每个行为都会在模型类中结束。这是我们知道(根据经验)是一个坏主意。成百上千将代码行塞进一个类是灾难的根源。服务对象就是在这种挫折中发展起来的。 - https://tmichel.github.io/2015/09/14/oo-controversies-tell-dont-ask-vs-the-web/

我们实际上有一个带有胖域模型的项目,它有这个确切的问题。随着需求随着时间的推移而变化并且代码变得更加复杂,一个巨大的胖模型对于执行不同的操作和处理新的需求是非常不灵活的。与其在同一个简单数据模型上添加不同的新服务工作流路径(类),您必须对庞大、复杂的域模型进行昂贵、困难的重构。它封装了数据并防止任何人以意想不到的方式修改数据,但同时也使得新的工作流程很难以新的方式操作数据。

【讨论】:

  • 我支持贫血域模型的同事抱怨验证不力会导致错误数据和重复代码在某些服务中得到修复,但在其他服务中却没有得到修复。但我会争辩说,无需将所有内容都放入域对象中即可解决这些问题。
猜你喜欢
  • 2017-10-26
  • 1970-01-01
  • 2023-03-15
  • 1970-01-01
  • 1970-01-01
  • 2013-03-03
  • 2021-08-04
  • 2011-05-07
  • 1970-01-01
相关资源
最近更新 更多