仓储层应该放在哪一层?

9

存储库类应该放在哪一层?领域层还是基础设施层?


DDD主张领域层不涉及任何基础设施问题。基础设施层是存储库的理想位置。要使用存储库,只需从服务层引用它们的接口即可。 - some1 here
4个回答

23

存储库的 接口 是领域的一部分。 接口的实际 实现 应该是基础设施的一部分。


定义分层架构的规则怎么样(请参考我的答案)? - Rogério
1
很好的问题。存储库接口在数据定义模块中定义,该模块在域层和基础设施层之间共享。这意味着一种松散的分层形式。正如《软件架构模式》一书第39页所述:“J层提供的函数的参数、返回值和错误类型应该是编程语言内置类型、在J层中定义的类型或从共享数据定义模块中获取的类型。请注意,共享在层之间的模块会放宽严格分层原则。” - Jonas Kongslund
1
你所指的书是《面向模式的软件架构》,第39页的内容也可以在这里看到,其中第4步中提到的“在层之间共享的数据定义模块”是一个领域实体类型,而不是仓储接口。在DDD中,领域实体属于领域层(请参阅DDD书籍)。即使我们将实体放入基础设施层,也没有任何“面向模式”的书籍说组件在“J”层可以依赖于更高的“J+1”层。 - Rogério
@manymanymore,你从哪里得到“存储库接口不应该是领域层的一部分”的说法?《领域驱动设计》这本书在第70页中提到了领域层:“这里控制和使用反映业务情况的状态,尽管存储它的技术细节被委托给基础设施。”而在第124页中:“虽然存储库和工厂本身并不来自领域,但它们在领域设计中具有重要角色。这些构造完成了模型驱动设计...”存储库的接口实现都属于领域模型/层。 - Rogério
@manymanymore 领域层的目的是将定义领域实体的代码封装在一个或多个有界上下文中,并实现相关业务逻辑。在DDD中,该代码分布在实体、值对象、领域服务、工厂和存储库的面向对象类之间。领域层使用其下方的基础设施层,并被其上方的UI和应用程序层所使用/知晓。 - Rogério
显示剩余3条评论

2

我想这取决于您将如何依赖它们。

问题是 - 您是否允许自己从域内使用存储库?
如果是这样的话,那么您就被迫把它们放在里面。

我个人喜欢把它们放在域外。因此,某些东西的典型生命周期如下所示 =>

UI => 控制器 => 从存储库检索聚合根 => 通过聚合根调用逻辑 => 如果创建了新的聚合根,则将其添加到存储库中。

有时控制器会调用应用程序服务,除了检索根并在其上调用函数之外,还会执行一些其他操作。但思路是相同的 - 领域不知道持久性。


虽然(在我看来)在域中放置存储库(或至少它们的抽象)没有什么问题,但它会使您的域更加意识到持久性。有时这可以解决问题,但通常情况下 - 这肯定会使您的域更加复杂。

使用对您而言更自然的方式,并随时准备切换您的方式。


我认为这取决于我们是否需要分层架构;如果需要,那么就没有选择:存储库放在领域层中。 - Rogério
我猜这取决于你将如何依赖它们。那是一个错误的猜测。根据DDD,领域层应该清楚地摆脱任何基础设施问题,没有其他选择。请阅读我的答案以获取更多细节。 - manymanymore
“@manymanymore”的“答案”毫无意义。我在那里的评论中解释了为什么,对于那些感兴趣的人。 - Rogério

2

仓储实现类与其单独的接口(如果存在)应该放在领域层中。

原因是遵循分层架构中的基本规则:低层不应依赖高层

如果我们接受这个规则(否则它就不是一个分层架构),那么将存储库实现放入基础结构层将使其依赖于领域层,从而违反了分层的基本规则。

例如,当我们创建一个新的领域实体时,我们将其放在领域层中;由于存储库(包括其接口和实现)不可避免地依赖于领域实体,这意味着存储库也必须放在领域层中。否则,在领域层中添加/删除/修改领域实体时,我们将更改基础结构层。

其他问题,例如保持领域层“干净”并独立于持久性细节,可以通过在领域层内部的实现中使用适当的基础设施服务来实现。例如,在Java中,我们可以使用JPA以非常少的代码实现存储库,并且没有SQL / JDBC或特定于数据库的代码(是否使用JPA实现存储库真的是一个好主意是另一种讨论;在任何情况下,JPA实体将使用JPA注释)。

参考资料:维基百科, MSDN


5
使领域层依赖基础设施层会使领域层变得脆弱,不能在应用程序中重复使用。通常,在构建领域模型时,人们希望独立于基础设施,这样可以反转依赖关系。基础设施必须依赖于领域模型,而不是领域模型依赖于基础设施。 - Shade
也许我有些误解 - 我所指的是来自Clean Architecture(https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)的概念,它与领域驱动设计兼容。在Clean中,领域是最内层,没有任何依赖关系。它确实包含所有领域逻辑,但没有应用程序特定的逻辑和基础设施问题。为了更好地理解,您有什么例子可以提供吗? - Shade
1
我现在桌子上有Evans的书,你能指出具体哪些部分与Clean不兼容吗?阅读第70页最下面的段落,它说:“将与域模型相关的所有代码集中在一层,并将其与UI、应用程序和基础架构代码隔离开来……将域层与基础架构和UI层分离可在每一层上实现更清晰的设计。”我的理解是,这意味着域层不应该依赖于任何其他层,这也是Clean等方法的前提。 - Shade
1
在这个上下文中,“depend”表示编译时依赖。因此,领域层中的组件将调用下面基础设施层中的组件的方法。这就是DDD书中描述的内容,并且在实际项目中也是常见的做法(根据我的经验)。在DDD中,Repository不是基础设施组件,而是领域组件;DDD为领域模型定义了五种这样的组件:Entity、Value Object、Factory、Domain Service和Repository。 - Rogério
@Rogério,恰恰相反。书籍是我了解DDD知识的主要来源。说领域层可以有基础设施问题,这表明您根本不理解领域层的意图。这就是为什么我怀疑您对DDD的了解程度。 - manymanymore
显示剩余5条评论

2

实现仓储类应位于基础架构层。仓储接口应该在服务层中。

领域层不应了解仓储库。DDD的战略模式指出,领域层应始终与概念模型保持同步。这意味着领域层应将众所周知的领域过程转换为代码,反之亦然。而领域过程是您的领域专家应该熟悉的内容。领域专家对仓储库一无所知。

另一种思考方式是,假设我们将仓储库或仓储库的接口放入领域层中。也就是说,现在我们在领域层代码中有了仓储库的概念。此外,领域层应该与领域的概念模型保持同步。因此,让我们问自己仓库在概念模型中的表示是什么。正确答案是概念模型中没有仓库。因此,仓库不能在领域层中。

尽管如此,我仍然遇到了将仓库放在领域层中的项目,并且项目上的工程师仍然称其为DDD实践。我认为问题在于人们没有重视DDD的核心战略模式,而只是玩弄可能会稍微简化编码工作的战术模式。


1
你能展示一些证据来支持你的说法吗?例如在DDD书中的哪里?或者其他作者/来源?还是这只是你个人的观点? - Rogério
@Rogério,让我们从简单的事情开始,这将有助于你更好地理解。域层的目的是什么?你能回答一下吗? - manymanymore
@Rogério,我在我的回答中也回答了你的所有问题。请仔细阅读后再提出已经在上面回答过的问题。 - manymanymore
在DDD中,领域层包含领域模型的所有源代码,包括实体、值对象、工厂、领域服务和存储库。DDD定义了一个四层架构,最上面是UI层,然后是应用程序层,然后是领域层,最后是基础设施层。编译时依赖关系只允许从高层到低层。你根本没有回答我的问题,我问的是参考资料,你没有。而且,我也没有说DL可以有“基础设施问题”。请阅读我回答中的最后一段,我在那里给出了一个具体的例子。 - Rogério
哦,所以你承认没有任何来源或参考资料?(真是个惊喜!)如果你想要一个逻辑上自洽的证明来证明你是错的,我可以给你一个:一个仓储接口依赖于实体类型(你同意这一点,对吧?),因此它在编译时依赖于领域层(所有实体类型都存在于此,对吧?)。基础设施层位于领域层之下(我们也同意这一点,对吧?)。因此,仓储接口不能在基础设施层中定义,因为这将违反分层架构的基本规则(即,依赖关系只能从顶部到底部)。 - Rogério
1
@Rogério,你听说过映射吗?例如AutoMapper。你知道它是用来做什么的吗?“基础设施层在领域层之下”——这再次证明你对DDD一窍不通。领域层不应该依赖于任何东西——也就是说,在领域层之下没有任何东西。 - manymanymore

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