DDD 领域驱动设计落地实践系列:工程结构分层
引言
前面几篇文章中,笔者给大家阐述了 DDD 领域驱动设计的三大过程,重点围绕如何通过战略设计与战术设计进行 DDD 落地实践进行了详细的讨论,但是还没有涉及到工程层面的落地。实际上所有的这些架构理论到最后都是为了使得我们代码结构更加清晰,从而开发出 bug 少、扩展性强、逻辑清楚的应用。因此本文就是为了解决 DDD 领域驱动落地实践最后一公里问题,将我们分析出来的领域模型通过与工程结构的映射实现真正的落地。
DDD 领域分层
当我们完成领域模型构建之后,我们需要先确定下微服务内部的领域分层结构,因为这个领域分层的好坏直接决定了我们微服务的工程结构是否合理,调用逻辑是否清晰。而这些领域模型都需要映射到实际的代码,我们开发的代码到底应该放在哪一层,放在哪些目录中,都需要依托于领域封层的结果。但是真正的领域驱动设计在怎么规范代码结构上面实际也没有具体的规范,因此我们需要根据自己的实践经验以及思考进行划分。
分层的目的实际上就是让各层的逻辑可以分工协作、各司其职,避免不必要的代码互相污染。同时结构清晰的分层结构也比较便于后期的重构以及拆分或者合并,实际上也是一种在为未来可能存在的变化节省研发成本。
这里需要说明的是,在传统的领域分层中,是将基础设施层作为其他三层的依赖的,但是实际上这种方式是有问题的。为什么这么说呢?因为如果我们真的按照用户接口层、应用层以及领域层都依赖基础设施层的话,那基础层就成了最核心的层级了,但是实际上领域层才是真正的核心,这显然违背了 DDD 以领域为核心的设计思想。因此我们使用依赖倒置的方式,让基础设施层去依赖领域层,这样做的好处就是可以让领域层更加的职责单一以及更加的纯粹,他不需要关心数据是怎么来的,无论是数据库查来的还是缓存萃取出来的还是从外部查来的,它只需要关心它自己领域内的业务逻辑就可以。既然我们明确了该怎么进行领域分层,那么各层的数据组织形式是怎样的呢?
各层数据对象
VO(View Object,视图对象):该层的视图数据对象主要的作用就是将应用层的数据进行组装后形成用于页面展示的视图数据。
DTO(Data Transfer Object,数据传输对象):DTO 主要作为 Application 层的入参和出参,用于用户接口层与应用层之间的数据传输。
Model(领域对象):领域对象是我们正常业务应该用的领域业务模型,它的字段和方法应该和业务语言保持一致,不需要进行持久化和序列化,他主要存在与内存中。也就是说,所以 Model 和 DO 可能字段属性都不一样。
DO (Data Objec,数据对象):一般都是使用 PO 作为持久化的数据对象,但是笔者习惯使用 DO,因为我觉得数据对象当然要和数据库中的字段相对应的。因此从名称来说,DO 作为持久化对象我想更加合适一点。
数据流转
上文中分别说到了领域分层结构以及各个数据对象的不同含义和用途,那么我们接下来就看下各个数据对象在 DDD 的各个领域分层中是怎么进行数据流转的吧。
在用户接口层,它需要接收来自 WEB 端、APP 端以及其他的外部数据请求,并将请求通过 DTO 向应用层进行传递,根据应用层返回的 DTO 数据,再将 DTO 转化为页面需要呈现的 VO 数据,进行最后的页面展示。
当请求到达应用层后,如果需要调用外部服务的接口,那么我们需要通过应用层的防腐层进行调用。那么为什么需要防腐层进行调用而不是直接调用呢?主要的原因就是为了隔离接口数据变化,防止外在服务的数据变化影响应用层的代码,如果真的需要修改那么直接在防腐层中进行修改就好了。
在领域层,我通常使用的是 model,可以理解为业务领域模型,主要包括实体以及值对象。在应用层会将 model 作为参数进行领域层接口的调用完成核心的业务逻辑。在一些其他的书中,很多人喜欢使用 DO 来作为领域层的数据承载对象,但是我个人还是觉得 model 更适合,因为从名称上面更好理解一点,更加直观一点,一看就知道是模型,什么模型,当然是业务的领域模型。
领域层中包含了仓储的接口,具体的实现在基础层中,这是一种依赖倒置的设计方式,实现领域层与基础层的解耦。其他的书中喜欢使用 PO 作为这层的数据载体,我习惯使用 DO。因为 DO 就是数据对象,天然的和数据库应该对应在一起,笔者同样觉得这样更加直观。具体怎么用还是看各位在实际落地使用的时候的习惯吧。
领域模型与代码模型映射
工程分层
如上图所示,我们在微服务拆分后,将微服务内部的代码层级主要分为 interfaces、biz、domain 以及 instructure 这四层,分别对应用户接口层、业务层、领域服务层以及基础设施层,注意这里的 biz 层实际就是 application 层,用为笔者觉得 application 层不好理解,而 biz 层更好理解一点,实际他们的功能是一样的,只是为了减低下理解的成本。
interfaces 层:就是用户接口层,这里主要存放一些与前端交互的代码以及与其他服务交互的接口,主要的作用将将前端请求转化为适当数据去调用 biz 层的业务代码进行业务请求,另外将 biz 层返回的数据进行组装最终形成页面所需要的数据。
biz 层:其实就是 application 层,但是我更愿意叫 biz 层,因为这层的作用实际就是进行服务组合以及服务调度的,实际它是基于下层的领域服务去完成业务逻辑流程的,因此我觉得叫 biz 层更加贴切一点。
domain 层:这是整个微服务的核心层,主要包含了领域模型、领域服务以及核心的业务逻辑。领域模型主要包括了聚合中的聚合根、实体以及值对象,领域事件也会放在这一层中。
instructure 层:基础设施层主要包含了各种通用能力以及工具类,它的作用就是为上层提供基础服务的,如数据库服务、MQ 服务以及缓存服务等还有其他的一些配置以及资源。
代码归位
1、interfaces 层
(1)assemble 主要负责实现 vo 与 dto 之间的数据转换工作。
(2)vo 存放需要呈现在页面的视图数据对象。
(3)facade 主要实现本层的接口封装。
2、biz 层
按照我自己的习惯,笔者是按照互粉的领域来进行包构建的,比如这里的 user 以及 authority 就是不同的领域。为什么这么划分呢?主要考虑到未来万一有新的拆分,我们可以直接按照对应的域进行拆分,效率要比在不同包中一个一个类找相关的高的多。另外按照领域划分也比较清晰。可以方便的熟悉业务。
(1)dto 中主要放了数据传输对象,biz 层接口的入参以及返回值都是 dto。
(2)service 就是 biz 的接口以及接口实现,主要的作用就是实现对于领域服务的编排以及组合,从而完成具体的业务流程。
(3)event 主要放的是事件发布以及订阅的相关代码,实际具体的事件核心逻辑可以放在 domain 层中进行处理。
3、domain 层
说实话,以前我们经常使用以数据驱动的设计以及开发方式,大部分的业务逻辑都是在 biz 层完成的,但是领域驱动设计和数据驱动设计最大的不同就是,大部分核心的业务逻辑需要下沉到 domain 层中。实际一开始进行这种方式的开发的时候非常的不习惯,但是当你实际沉下心来做的时候会发现,的确这种开发方式更加的能体现 DDD 的优势。另外这里还是根据领域进行划分,和 biz 层是一样的。
(1)event 主要放事件处理的核心逻辑代码。
(2)model 中就是具体的实体。
(3)repo 主要存放持久化的代码。
(4)service 主要存放领域服务的核心业务逻辑代码。
(5)valueobject 主要存放值对象。
4、instructure 层
这层的话主要存放一些基础中间件的链接配置代码以及 domain 的 repo 实现代码以及其他一些资源类的代码。
总结
DDD 领域建模设计的理论中并没有对在工程结构怎么落地实际的代码做出硬性规定,本文主要根据笔者自己的一些思考以及实践经验梳理了领域模型与代码模型映射的结构,希望对大家再各自业务落地 DDD 实践的时候有所参考。到这里我们实现 DDD 落地的所有准备工作都差不多了,后面的文章中我们将根据真实的业务场景进行真正的落地实践,从业务分析到领域建模,从领域分层到代码实现。我想通过这一系列的理论加实践的方式,相信大家可以真正领悟到 DDD 的魅力所在。
版权声明: 本文为 InfoQ 作者【慕枫技术笔记】的原创文章。
原文链接:【http://xie.infoq.cn/article/204ef91b666c2be44d873585c】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论