什么是 DDD 分层架构?
DDD(领域驱动设计)的分层架构经历了持续的演进。最初是经典的四层架构;随后,四层架构得到了进一步优化,实现了各层与基础设施层的解耦;再到后来,在领域层和应用层之间引入了上下文环境(Context)层,从而形成了五层架构(DCI,Data-Context-Interaction)
图片
让我们来看一下这张图。在最早的传统四层架构中,基础层(Infrastructure Layer)被其他层所依赖,位于架构的核心位置。然而,按照分层架构的设计思想,领域层(Domain Layer)才是软件的核心,因此这种依赖关系显然存在问题。为了解决这一问题,我们引入了依赖倒置原则(Dependency Inversion Principle, DIP),对传统的四层架构进行了优化,成功实现了各层对基础层的解耦。
我们今天讲的 DDD 分层架构就是优化后的四层架构。在下面这张图中,从上到下依次是:用户接口层、应用层、领域层和基础层。那 DDD 各层的主要职责是什么呢?下面我来逐一介绍一下。
图片
1. 用户接口层
用户接口层的主要职责是向用户展示信息并解释用户指令。这里的“用户”不仅限于人类用户,还包括程序、自动化测试脚本以及批处理脚本等。
2. 应用层
应用层是一个相对较薄的层次,理论上不应包含具体的业务规则或逻辑,而是专注于用例和流程相关的操作。它位于领域层之上,负责协调多个聚合的服务和领域对象,完成服务的编排和组合,从而执行业务操作。此外,应用层也是微服务之间交互的通道,能够调用其他微服务的应用服务,实现跨微服务的服务组合和编排。
需要注意的是:在设计和开发过程中,应避免将本应属于领域层的业务逻辑错误地放到应用层中实现。如果应用层过于庞大,会导致领域模型失去焦点,久而久之,微服务可能会退化为传统的三层架构,业务逻辑变得混乱不堪。
应用服务位于应用层,其职责包括:
- 服务的组合、编排和转发;
- 处理业务用例的执行顺序以及结果的组装;
- 通过 API 网关向前端发布粗粒度的服务;
- 执行安全认证、权限校验、事务控制;
- 发送或订阅领域事件等。
3. 领域层
领域层是实现企业核心业务逻辑的关键层次,通过各种校验手段确保业务的正确性。它主要体现领域模型的业务能力,用于表达业务概念、业务状态和业务规则。领域层包含聚合根、实体、值对象、领域服务等领域模型中的核心对象。
领域对象的关系说明:
- 实体和领域服务是领域层中实现业务逻辑的主要组成部分。
实体通常采用充血模型,实现与其相关的所有业务功能。
当某些业务功能无法由单一实体(或值对象)实现时,领域服务会介入,组合聚合内的多个实体(或值对象),完成复杂的业务逻辑。
4. 基础层
基础层贯穿于所有层次,其主要职责是为其他各层提供通用的技术支持和基础服务。这些服务包括但不限于:
- 第三方工具和驱动;
- 消息中间件;
- API 网关;
- 文件存储;
- 缓存服务;
- 数据库等。
其中,数据库持久化是基础层最常见的功能之一。
基础层通过依赖倒置设计,封装基础资源服务,实现应用层、领域层与基础层的解耦。这种设计能够有效降低外部资源变化对应用的影响。
DDD 分层架构最重要的原则是什么?
DDD 分层架构有一个重要的原则:每层只能与位于其下方的层发生耦合。
根据耦合的紧密程度,架构可以分为两种类型:严格分层架构和松散分层架构。
- 严格分层架构:
- 优化后的 DDD 分层架构模型属于严格分层架构。在这种架构中,任何层只能依赖于其直接下方的层,依赖关系清晰且易于管理。例如:
领域服务只能被应用服务调用;
应用服务只能被用户接口层调用。服务是逐层封装或组合的,依赖关系明确,便于维护和扩展。
- 松散分层架构:传统的 DDD 分层架构属于松散分层架构。在这种架构中,某一层可以依赖于其下方的任意层,依赖关系复杂且难以管理。例如:
领域服务可能同时被应用层和用户接口层调用;
核心业务逻辑容易外泄,增加了维护和升级的难度。
DDD 分层架构如何推动架构演进?
1.微服务架构的演进
我们了解到领域模型中对象的层次从内到外依次为:值对象、实体、聚合和限界上下文。
- 值对象和实体的简单变更通常不会对领域模型和微服务产生重大影响。
- 然而,聚合的重组或拆分则可能引发较大的变化。这是因为聚合内的业务功能是高度内聚的,能够独立完成特定的业务逻辑。
当聚合发生重组或拆分时,业务模块和系统功能往往会随之发生变化。因此,我们可以以聚合为基础单元,推动领域模型和微服务架构的演进。
具体来说:
- 聚合的重组或拆分:聚合可以作为一个整体,在不同的领域模型之间进行重组或拆分。这种调整能够更好地适应业务需求的变化。
- 聚合独立为微服务:在某些情况下,可以直接将一个聚合独立为一个微服务。这种方式能够进一步提升系统的灵活性和可维护性。
图片
当你发现微服务 1 中聚合 a 的功能经常被高频访问,以致拖累整个微服务 1 的性能时,我们可以把聚合 a 的代码,从微服务 1 中剥离出来,独立为微服务 2。这样微服务 2 就可轻松应对高性能场景。
在业务发展到一定程度以后,你会发现微服务 3 的领域模型有了变化,聚合 d 会更适合放到微服务 1 的领域模型中。这时你就可以将聚合 d 的代码整体搬迁到微服务 1 中。如果你在设计时已经定义好了聚合之间的代码边界,这个过程不会太复杂,也不会花太多时间。
最后我们发现,在经历模型和架构演进后,微服务 1 已经从最初包含聚合 a、b、c,演进为包含聚合 b、c、d 的新领域模型和微服务了。
2.微服务内服务的演进
在微服务内部,实体的方法被领域服务组合和封装,领域服务又被应用服务组合和封装。在服务逐层组合和封装的过程中,你会发现这样一个有趣的现象。
图片
让我们来看一下这张图。在服务设计时,你可能无法完全预测哪些下层服务会被多少个上层服务组合调用。因此,领域层通常只提供一些原子服务,例如领域服务 a、b、c。
然而,随着系统功能的增强和外部接入的增多,应用服务会不断丰富。某一天,你可能会发现领域服务 b 和 c 被多个应用服务频繁调用,且它们的执行顺序基本一致。这时,你可以考虑将 b 和 c 合并,并将应用服务中 b 和 c 的功能下沉到领域层,演变为一个新的领域服务(b+c)。
这种演进方式不仅减少了服务的数量,还降低了上层服务组合和编排的复杂度。
三层架构如何演进到 DDD 分层架构?
通过前面的讲解,相信你已经对 DDD 分层架构的优势有了清晰的认识。我们可以总结出以下两个最重要的优点:
- 层间松耦合:DDD 分层架构通过解耦各层之间的依赖关系,使得我们可以专注于本层的设计,而无需过多关注其他层。这种设计不仅降低了层与层之间的耦合度,还避免了因某一层的改动而影响其他层的情况。
- 结构清晰,易于升级和维护:分层架构使程序结构更加清晰,升级和维护变得更加容易。当我们需要修改某一层的代码时,只要该层的接口参数保持不变,其他层通常无需进行任何改动。即使某一层的接口发生变化,也只会影响其直接相邻的上层,修改工作量较小且风险可控,不会引发意外的系统问题。
那我们该怎样转向 DDD 分层架构呢?不妨看看下面这个过程。
传统企业应用大多是单体架构,而单体架构则大多是三层架构。三层架构解决了程序内代码间调用复杂、代码职责不清的问题,但这种分层是逻辑概念,在物理上它是中心化的集中式架构,并不适合分布式微服务架构。
DDD 分层架构中的要素其实和三层架构类似,只是在 DDD 分层架构中,这些要素被重新归类,重新划分了层,确定了层与层之间的交互规则和职责边界。
图片
让我们来看一下这张图,分析从三层架构向 DDD 分层架构演进的过程。
1. 演进的核心区域
从三层架构向 DDD 分层架构的演进,主要集中在业务逻辑层和数据访问层。
2. 用户接口层的变化
DDD 分层架构在用户接口层引入了 DTO(数据传输对象),为前端提供了更多的可用数据,并提升了展示的灵活性。
3. 业务逻辑层的优化
DDD 分层架构对三层架构的业务逻辑层进行了更清晰的划分,解决了三层架构中核心业务逻辑混乱、代码改动相互影响大的问题。具体来说:
- 应用层:快速响应前端的变化,负责服务的组合、编排和转发。
- 领域层:实现领域模型的能力,专注于核心业务逻辑的表达。
4. 数据访问层的改进
在数据访问层和基础层之间,DDD 分层架构引入了仓储(Repository)设计模式,取代了三层架构中的 DAO 方式。仓储模式通过依赖倒置原则,实现了各层对基础资源的解耦。仓储分为两部分:
- 仓储接口:位于领域层,定义数据访问的契约。
- 仓储实现:位于基础层,负责具体的数据库操作。
此外,三层架构中通用的第三方工具包、驱动、Common、Utility、Config 等公共资源类,在 DDD 分层架构中被统一放到了基础层。
5. 演进的意义
传统三层架构向 DDD 分层架构的演进,体现了领域驱动设计思想的逐步成熟和应用。这种演进不仅优化了架构的分层设计,还提升了系统的灵活性、可维护性和可扩展性。