4. DDD 版的分层架构解读

DDD 的分层架构看上去和 DDD 有点关系,但实际上关系并不是特别大。DDD 核心还是聚焦领域层,只是希望通过分层,"避免将领域概念和其他只与软件技术相关的概念搞混了"。实际上是为了隔离业务语言和技术语言。

DDD 里面强调在同一个限界上下文里面保持统一语言,统一语言也就是业务语言。在一个限界上下文内,为了保证统一语言的纯粹,也要通过分层来隔离技术语言。所以分层在 DDD 里面也是必不可少的。

DDD 的分层架构有 2 个版本,会先介绍 <领域驱动设计> 的分层和问题,再介绍 <领域驱动设计实现> 里面如何解决这些问题。

<领域驱动设计> 里面的DDD 分层架构

下面的分层是 <领域驱动设计> 的原图

基础设施层

我们先看基础设施层,其实基础设施层相当于端口和适配器里面的适配器。以前普通三层架构只有一个数据访问层,但随着技术的发展出现越来越多的中间件,因此数据访问层已经不适用了,需要把含义扩大,变成基础设施层。基础设施层属于软件开发领域的基础设施,也就是会包含软件领域的一些实现细节,例如数据库访问,mq发送,缓存访问等等。

但这里的基础设施层有问题,那就是基础设施层比较抽象,比较难指导落地。相对来说端口和适配器架构在适配器层会有几个分类,也有分主动和被动适配器,会更容易让人理解。因此如果要落地基础设施层,最好能在实践的时候对基础设施进行分类。

其实基础设施层已经有对主动和被动适配器进行分类了,只是没有明说。右边是主动适配器,对于主动适配器来说,需要提取接口。左边是被动适配器,对于被动适配器,直接调用即可。(因为层的依赖有问题,所以和端口和适配器架构的依赖倒置是反的,变成领域层可以直接调用基础设施层(被动),而基础设施层调用领域层还要依赖倒置(主动))。

技术设施层的依赖关系也有比较大的问题,其他层都依赖了技术设施层。我们前面也有说过这不合理。领域层应该是最有价值,最应该在分层里面是稳定的。

用户界面层

用户界面层更像 MVC 里面的 Controller。负责解耦 Model 和 View,避免他们相互依赖。Model 和 View 都有自己的模型,Controller 负责模型的绑定和转换。

例如上面介绍的技术设施层作用,"为用户界面层绘制屏幕组件",而用户界面层则负责向用户显示信息。如果说应用层负责提供信息,技术设施层负责构造屏幕组件,那用户界面层则负责信息和屏幕组件的整合。但有点不能理解的是应用层和领域层都依赖了基础设施层。本来Controller的作用是为了解耦 Model 和 View,但如果 Model 直接依赖 View,那 Controller 作为胶水就没什么意义了,反正都依赖了基础设施层,直接把胶水代码也写到应用层或者领域层就可以了。

从另一个角度看,现在愈发流行前后端分离,用户界面层的职责更多是被前端承接,所以也就没有存在的必要。在 Spring 里面 Controller 的职责只是负责 Http 协议的转换,可以归为技术设施层。其实涉及到协议转换的基础设施还有很多,例如 mq 发送,消息推送等,http 只是其中一种。

因此我认为用户界面层其实没有存在的必要,把它合并到基础设施层更合适,如果技术设施层分为主动基础设施层和被动基础设施层,则应该并到主动基础设施层。

领域层

领域层是最重要的一层,是需要重点关注的。我们分层最主要的作用就是避免领域层遭受技术语言的入侵。按照正确的依赖关系,也就是基础设施依赖领域层,基础设施层包含了业务语言和技术语言。但领域层则不能包含技术语言。前面讲端口和适配器架构的时候已经有讨论这一点了,这里就不重复了。

DDD 里面的领域层会更加具体一些,虽然架构图没有说明,但书中强调了 DDD 一些概念属于领域层,例如 聚合,实体,值对象,领域服务,仓储等。这样就更容易落地一些。而 DTO,VO 等则不属于这一层。

应用层

应用层的作用是什么呢? 和领域层的区别是什么?

1. 面向用例,对领域层进行编排

领域层的逻辑是粒度较小的,偏通用一点的。当业务比较复杂,涉及到多个领域的时候,就需要对领域层进行组合。例如用户注册逻辑,用户注册成功之后要添加积分,构造默认模板数据。其实"注册","添加积分"和"构造默认模板数据"分别属于3个领域的逻辑,用户领域,积分领域,模板领域,而且相对比较定制一些,写在任何一个领域里面都不合适,因此适合放到应用层,由应用层进行编排和组装。可以看出应用层粒度会更大一些,更个性化一些,不含有业务逻辑。而领域层会更小一些,更通用一些。

但问题在于规则并不是特别清晰。例如上面的例子,如果逻辑变成,在某几天内注册或者有邀请码才能添加积分。这个因为有了逻辑判断应该属于领域规则,按规则不应该写在应用层。而应该写在领域层里面的领域服务(这里说明一下 DDD 里面的领域服务是为了实现不同聚合的交互逻辑,因为涉及到不同聚合的交互写在哪个聚合里面都不合适,因此需要有领域服务)。这样其实就很容易导致修改,分层的成本就很高。并且有时候也很难说明白是要放到哪里,会导致应用层和领域层都有逻辑,很难看懂。

2. 业务能力的体现,充当系统门面

应用层体现了业务能力,也就是系统能完成的功能,例如上面提到的注册可能会分很多步,但对于系统的使用方来说,它不用太关心注册的细节,不关心用例下面的领域逻辑。所以应用层可以很好的充当门面功能。

门面是为了屏蔽细节,因此应用层的参数就不能直接使用领域层的对象,因为领域层的对象基本上都是充血模型,包含了一些业务细节。因此应用层需要再定义新的类型,暂时称为 BO (business object)。一般是贫血模型,也有可能会有简单的类型转换或者简单的参数验证,不会有业务逻辑。这时应用层还要承担着把 BO 转成领域对象的职责,例如转成聚合,值对象。

3. 对请求进行通用处理 (AOP)

一个完整的系统需要有一些通用能力,对请求进行一些通用拦截。而应用作为门面很适合承担了这部分职责。例如日志打印,事务管理,参数校验(非领域相关的,例如参数类型,长度,是否存在),用例兼容 (例如新用例需要多1个参数,旧用例需要在应用层给这个参数加上默认值,然后就可以调用同一个领域方法了)。

有了应用层的保驾护航,领域层就可以更专注业务了。

应用层总结

因为作用 1 难以区分应用和领域,其实起不了多大的作用,所以我认为可以不用太纠结代码应该写在应用还是领域层。除了 2,3 之外,都可以在领域服务里面实现逻辑,这也导致应用层会相对薄一些。但因为作用 2 还是很重要,所以还是不能去掉这一层。

问题

最大最大的问题就是依赖的问题,依赖过于混乱。

上层可以调用下面每一层

这其实是一个松散的分层架构,应用层直接依赖领域层和基础设施层实可以接受,但用户界面层直接调用领域层和基础设施层就不能接受了。因为前面也有提到,应用层应该充当门面的功能。如果用户界面层直接调用基础设施层,就跳过门面了,引用层作用 2,3 都失效了,这个不合理的。除非当初压根不把应用层当门面,只是做个编排用。

全部依赖基础设施层

之前也有提到,就不再累赘了。

<领域驱动设计实现> 里面的DDD 分层架构

在<领域驱动设计实现> 里面,作者对原来的分层架构进行改进。

解决了2个问题

  1. 领域层应该是稳定的,应该被其他层依赖。
  2. 用户接口层不再依赖领域层。

基础设施层依赖领域层也是没问题的,并不是说基础设施层会去调用领域层,而是通过领域层去调用基础设施层的时候,通过依赖倒置的方式让基础设施层反向调用领域层。其他层也是一样的,被动基础设施层都需要依赖倒置来解决依赖问题。这里在前面的文章里面也有提到,就不赘述了。

所以 <领域驱动设计实现> 的分层架构才是比较合理的。

总结

DDD 的分层架构同样和之前讨论的分层架构大同小异。用户界面层(因翻译不同或称用户接口层)其实经过分析不是很有存在的必要。剩下的应用层,领域层,基础设施层都是熟悉身影。但 DDD 对于领域层会有更深的理解和更明确的定义,这对于区分应用层和领域层是非常有好处的。如果对 DDD 有深入的理解应该可以体会到领域层的与众不同,对为什么要保证领域层是稳定的有更深的体会。

摘要 DDD 的分层架构和整洁架构有相似之处,也有自己的侧重点。DDD 的分层架构核心需要关注的是领域层和其他层的区别和边界,因为领域层才是 DDD 的精髓。

相关推荐
XINGTECODE36 分钟前
海盗王集成网关和商城服务端功能golang版
开发语言·后端·golang
天天扭码41 分钟前
五天SpringCloud计划——DAY2之单体架构和微服务架构的选择和转换原则
java·spring cloud·微服务·架构
程序猿进阶42 分钟前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
FIN技术铺1 小时前
Spring Boot框架Starter组件整理
java·spring boot·后端
余生H1 小时前
transformer.js(三):底层架构及性能优化指南
javascript·深度学习·架构·transformer
凡人的AI工具箱1 小时前
15分钟学 Go 第 60 天 :综合项目展示 - 构建微服务电商平台(完整示例25000字)
开发语言·后端·微服务·架构·golang
先天牛马圣体1 小时前
如何提升大型AI模型的智能水平
后端
java亮小白19971 小时前
Spring循环依赖如何解决的?
java·后端·spring
2301_811274312 小时前
大数据基于Spring Boot的化妆品推荐系统的设计与实现
大数据·spring boot·后端
运维&陈同学2 小时前
【zookeeper01】消息队列与微服务之zookeeper工作原理
运维·分布式·微服务·zookeeper·云原生·架构·消息队列