我们应该在 DDD 中使用“按功能打包”结构吗?

Posted

技术标签:

【中文标题】我们应该在 DDD 中使用“按功能打包”结构吗?【英文标题】:Should we use 'package by feature' structure with DDD? 【发布时间】:2019-08-10 06:15:18 【问题描述】:

在做了一些研究之后,我确认folder-by-feature 结构在大多数情况下优于folder-by-layer 结构。要获得一些论点,我们可以阅读以下文章甚至this answer。

Package by features, not layersFeature folders vs Tech foldersPackage by feature, not layerPackage by Layer for Spring Projects Is Obsolete

但是,我发现的所有 DDD 项目示例都是使用 逐层打包 制作的,大部分时间都遵循以下结构:

├── 应用 ├── 配置 ├── 域名 ├── 基础设施 └── 接口

所以我的问题是:为什么 DDD 社区不遵循 package-by-feature,即使它在大多数情况下明显优越?

我们应该在 DDD 中使用 package-by-feature 吗?如果有,该怎么做?

我提到我不是在谈论关于微服务架构的特殊情况,显然逐层封装更多相关的。

【问题讨论】:

【参考方案1】:

我对这个主题的理解和看法如下:

按功能打包是关于垂直切片(根据领域概念构造源代码)而不是水平分层(根据技术概念构造)。

但是说“代替”并不完全正确,因为有时您必须区分这些技术概念。最好是先按功能封装,然后在每个功能内部逐层封装。

在战略性 DDD 中,每个限界上下文 (BC) 都是一个垂直切片(整个应用程序、完整堆栈...具有 UI、应用程序、域模型和基础架构层)。

然后,在 BC 内部,战术 DDD 提倡首先按业务概念打包领域模型代码,然后再按技术概念(战术模式)打包。所以你有模块(紧密结合和松散耦合的聚合组),并且在每个聚合内部都有实体、值对象、工厂、存储库。其他层(UI、应用程序、基础设施)也可以由模块构成。

因此,作为总结,DDD 确实遵循混合方法:

按不同粒度级别的业务概念打包:BC、模块、聚合。

在 BC 中逐层打包:UI、应用程序、域、基础设施。

PD:这个主题(源代码结构)在 Vaughn Vernon 的书“实施 DDD”的第 9 章(模块)中进行了解释。

【讨论】:

许多“按功能打包”的推广者似乎提倡按功能打包,因为***结构更好(在 BC 内),但我不确定 someAr/application/UserApplicationService 是否比application/someAr/UserApplicationService. @plax 在我的回复中,我的意思是您的第二个选项:在 BC 内部,将每一层构造成模块。我认为您的第一个选项也是 Vernon 在他的书中提到的另一种可能性,当在我们的整个领域中不是很清楚划分(语言)到 BC 时,它就会被应用。它被称为 «Module before BC»(本书第 344 页)。 我会再看一遍那章,已经有一段时间了。感谢您的参考。 @plalx 你很好。你比我更了解 DDD。现在重新阅读该章节部分(因为它对我来说也很长时间),我认为这不是你的意思。 “BC 之前的模块”所说的是,如果我们对将一个域模型粗化为两个模型的语言划分不够确定,或者不清楚,我们不应该创建 2 个 BC(每个模型一个),而是创建 2 个模块只保留一个模型。我认为这是一个子域:BC 关系不是 1:1 而是 2:1 的情况,因为我们只使用一个 BC(模型)实现 2 个(“模糊”)子域,每个子域都是一个模块。【参考方案2】:

DDD 是关于域的,因此将所有内容分组到域文件夹中。但在非常高层保留 4 个六边形架构层。这可能是为了简单起见的包或具有严格相互依赖关系的最佳独立模块(如在 android Instant Apps 中)。

src/main/java
├── UserInterface
│   ├── OneUseCase.java
│   ├── AnotherUseCase.java
│   └── YetAnotherUseCase.java
├── Application
│   ├── SubDomain1
│   │   └── ... (java files)
│   └── SubDomain2
│       └── ... (java files)
├── Domain
│   ├── SubDomain1
│   │   └── ... (java files)
│   └── SubDomain2
│       └── ... (java files)
└── Infrastructure
    ├── SubDomain1
    │   └── technology1 (java files)
    │   └── technology2. (java files)
    └── SubDomain2
        └── technology1 (java files)
        └── technology2. (java files)

这是一个a good article 清晰的解释。

【讨论】:

【参考方案3】:

为什么 DDD 社区不按功能进行包

我想你会发现好的项目确实会按功能打包。

示例中,您可能会发现它没有被遵循。慷慨的解释是,作者试图让人们更容易识别关注点的分离和依赖箭头的方向。

不够慷慨?作者没有注意,或者不知道更好。

我们应该在 DDD 中使用 package-by-feature 吗?如果有,该怎么做?

是的,如果您执行 DDD,您将按功能打包的方式几乎相同。它们是正交关注点。

【讨论】:

谢谢!您是否有一个遵循 package-by-feature 的优秀 DDD 项目的链接?即使 this example 基于 Eric Evans' book 中使用的货物示例,this one 使用 Spring 或 this one 使用 Symfony 也不遵循它。 我真的不明白如何在没有包作为边界的情况下进行干净的域隔离。您是否建议不应存在 applicationdomain 包,并且您应该将域类与应用程序服务混合在一起?对我来说,似乎按层然后按特征似乎是最合乎逻辑的方法。您也可以按功能然后按层进行,但是能够将域类与应用程序的其余部分完全分离或能够清楚地识别应用程序入口点(应用程序服务)似乎非常重要。

以上是关于我们应该在 DDD 中使用“按功能打包”结构吗?的主要内容,如果未能解决你的问题,请参考以下文章

DDD - 持久性模型和领域模型

在 DDD 中公开 ASP.NET 身份服务

ORM中的Model与DDD中的DomainModel

我应该将使用 DDD 和 CQRS 方式刷新访问令牌的代码放在哪里?

在 DDD 中将存储库实现保存在哪里?

DDD 域实体与持久性实体