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 的精髓。

相关推荐
攸攸太上6 分钟前
Spring Gateway学习
java·后端·学习·spring·微服务·gateway
罗曼蒂克在消亡23 分钟前
graphql--快速了解graphql特点
后端·graphql
潘多编程25 分钟前
Spring Boot与GraphQL:现代化API设计
spring boot·后端·graphql
大神薯条老师1 小时前
Python从入门到高手4.3节-掌握跳转控制语句
后端·爬虫·python·深度学习·机器学习·数据分析
2401_857622662 小时前
Spring Boot新闻推荐系统:性能优化策略
java·spring boot·后端
AskHarries2 小时前
如何优雅的处理NPE问题?
java·spring boot·后端
_.Switch3 小时前
Python机器学习框架介绍和入门案例:Scikit-learn、TensorFlow与Keras、PyTorch
python·机器学习·架构·tensorflow·keras·scikit-learn
计算机学姐3 小时前
基于SpringBoot+Vue的高校运动会管理系统
java·vue.js·spring boot·后端·mysql·intellij-idea·mybatis
猿java4 小时前
Cookie和Session的区别
java·后端·面试
程序员陆通4 小时前
Spring Boot RESTful API开发教程
spring boot·后端·restful