DDD领域驱动设计如何进行工程化落地

简介: DDD领域驱动设计到底如何进行实际的工程化落地,为什么要进行领域分层?本文主要围绕DDD领域分层,设计了可落地的工程结构。

引言

前面几篇文章中,笔者给大家阐述了DDD领域驱动设计的三大过程,重点围绕如何通过战略设计与战术设计进行DDD领域模型分析以及沉淀,但是还没有涉及到工程层面的落地。所有的这些架构理论或者设计模式到最后都是为了让我们的代码结构更加清晰,扩展性以及维护性更强。从而开发出bug少稳定性更好的应用。因此本文重点介绍如何进行DDD工程化落地。


DDD领域分层


当我们完成边界上下文的划分以及领域模型的构建之后,就需要进行微服务的工程结构设计了。在进行工程结构落地之前,我们需要先确定微服务内部的领域分层结构。首先我们要思考一个问题,为什么要进行领域分层呢?实际上领域分层就是一种分而治之的思想,主要为了避免将代码工程开发成一坨大泥球,各种业务复杂逻辑以及技术细节都糅合在一起,导致工程后期难以维护同时也会领域模型的完整性。另外通过领域分层设计,更加容易开发出高内聚低耦合的软件服务,在模块复用以及扩展性方面也会有更好的表现。

搞清楚为什么进行领域分层之后,我们来确定下如何进行微服务内部的领域分层,因为分层设计的好坏直接决定了我们微服务的工程结构合理性以及后期团队落地的效果。不过遗憾的是,真正的领域驱动设计在怎么规范工程结构上面实际也没有非常明确具体的规范,因此我们需要根据自己的实践经验以及思考和理解来进行划分设计。下图中左边的分层方式是 Eric Evans在《领域驱动设计》中提出的,但是这种分层方式实际上是存在明显不足的。为什么这么说呢?


image.png


大家都比较熟悉MVC的开发方式,因此在团队中进行DDD落地的时候,很多同学有疑问为什么要让基础设施层反向依赖领域层呢,大家都觉得很别扭。按照正常逻辑来说,领域模型发生变化后需要进行持久化保存,很明显是领域层依赖基础设施层,但是在工程落地的时候还是基础设施层依赖领域层,这是为什么呢?实际上无论是什么样的架构都遵循这样的设计原则,我们都认为业务领域是核心域,核心域对外部的依赖越少越好,因此需要实现将技术复杂度与业务复杂度相分离。那么在 基于DDD的架构中,领域层就是核心层因此它的对外依赖越少越好,也就是说应该是非核心依赖核心而不是核心依赖非核心。

在我们以往的开发模式中,一般都是service接口去调用dao接口进行相关的数据操作,但是我们发现一旦我们进行一些优化操作,比如增加缓存来提升数据查询的效率,我们就需要修改service层的代码,但是实际上增加缓存属于技术实现细节,并不在业务范畴之内,可实际情况就是技术细节有变化就会影响到业务层,因此这样的状况明显是不合理的。


因此上图中优化后的依赖倒置,表面上是基础设施层依赖领域层,其本质是技术实现细节依赖于接口抽象,这是一种编程思想的转变。将repo层的接口定义在domain层,具体实现细节由基础实施层去完成,这样实现了对于技术实现细节的解耦。同时不仅保证了domain层模型的稳定性,也提升了基础设施层实现的灵活性。

image.png

各层数据模型对象

在介绍各层对象之前,我们先思考一个问题。为什么每一层都要有不同的数据模型对象呢?不同分层在进行接口调用的时候,每次都要进行模型对象转换,很多时候对象中的参数还都是一样的,这样做不是多此一举吗?这也是我在团队中推行DDD领域驱动设计落地的时候,很多同学提出来的疑问。

但是大家有没有想过一个问题,假设我们使用一个模型数据对象来串接代码中的各个分层,如果哪一天数据库表字段增加了或者修改了,那么这个变化会在各个分层中蔓延开来,这样即使做了应用分层但是实际上和一个大泥球的应用没有什么本质区别,另外对于核心的领域层来说也需要屏蔽底层细节变化对于领域模型的影响,避免领域模型稳定性问题。因此为了避免上述问题的发生,各个分层应该都有数据自己的模型数据对象,各司其职。


image.png

VO(View Object,视图对象)该层的视图数据对象主要的作用就是将应用层的数据进行组装后形成用于页面展示的数据。


DTO(Data Transfer Object,数据传输对象):DTO主要作为Application层的入参和出参,用于用户接口层与应用层之间的数据传输。比如接口参数中的Command、Query以及事件Event,以及Request、Response等都属于DTO的范畴。DTO的价值在于适配不同的业务场景的入参和出参,避免让业务对象变成一个万能大对象。


Model(领域对象):领域对象是我们常说的核心的领域模型对象,它的字段和方法应该具备强烈的业务语义,和持久化方式无关。也就是说,Entity和PO很可能有着完全不一样的字段命名和字段类型,甚至嵌套关系。Entity的生命周期应该仅存在于内存中,不需要可序列化和可持久化。


PO (Persistent Objec,持久化对象):实际上是我们在日常工作中最常见的数据模型。但是在DDD的规范里,PO应该仅仅作为数据库物理表格的映射,不能参与到业务逻辑中。为了简单明了,PO的字段类型和名称应该和数据库物理表格的字段类型和名称一一对应,这样我们不需要去跑到数据库上去查一个字段的类型和名称。

各层数据流转

各层数据流转

上文中分别说到了领域分层结构以及各个数据对象的不同含义和用途,那么我们接下来就看下各个数据对象在DDD的各个领域分层中是怎么进行数据流转的吧。

在用户接口层,它需要接收来自WEB端、APP端以及其他的外部数据请求,并将请求通过DTO向应用层进行传递,根据应用层返回的DTO数据,再将DTO转化为页面需要呈现的VO数据。

我们通过Query对象表示查询,用Command对象表示数据操作。当请求到达应用层后,如果需要调用外部服务的接口,那么我们需要通过应用层的防腐层进行调用。为什么需要防腐层呢?主要就是为了隔离变化,防止外在服务的数据变化影响应用层的代码,如果真的需要修改那么直接在防腐层中进行修改就好。

在领域层,我通常使用的是model,可以理解为业务领域模型,主要包括实体以及值对象。在应用层会将model作为参数进行领域层接口的调用完成核心的业务逻辑。在一些其他的书中,很多人喜欢使用DO来作为领域层的数据承载对象,但是我个人还是觉得model更适合,为从名称上面更好理解一点,更加直观一点。

领域层中包含了仓储的接口,具体的实现在基础设施层中,这是一种依赖倒置的设计方式,实现领域层与基础层的解耦。大致的数据转化流向如下图所示。



image.png



工程结构落地

在确定好领域分层各层的依赖关系之后,我们需要设计下具体可落地的工程结构,如下图所示。


image.png


starter层该层属于用户接口层,服务的启动类也在该层,主要负责服务的启动以及对外提供REST接口或者RPC接口。

business层:主要负责业务逻辑的编排,不负责具体的业务逻辑,因此该层应该是比较薄的。

integration层:ACL层,即防腐层,主要与外部服务接口进行交互,它的存在主要为了将微服务本身的业务模型与外部服务的模型进行隔离,避免外部服务模型的变化影响到自身服务领域模型的稳定性。

domain层:领域层属于核心层,所有的业务领域模型以及领域服务都在该层,沉淀了整个业务域中的业务领域模型,也就说核心的业务逻辑都落在此层,同时定义了repository层的接口。

common层:通用层,主要放一些支撑其他业务的代码,比如各种工具类,各种常量定义、错误码定义以及多语言等。

repository层:属于基础设施层,主要负责与数据库、Redis等进行交互,实现领域层定义的接口。


总结

本文主要和大家聊了怎样进行DDD领域驱动设计的落地,分析了为什么要进行领域分层以及为什么要实现依赖倒转的领域分层结构,同时基于依赖倒转的领域分层结构设计了可落地的微服务工程结构。希望通过本文可以为大家在落地DDD的时候提供一点工程结构设计的思路。后面的文章将从代码层面入手和大家分享下如何通过代码实现DDD落地。


相关文章
|
缓存 架构师 Java
架构师必备 - DDD之落地实践
今天带大家认识下DDD,一个听起来很垃圾却真的很牛X的设计思想,架构师必备!
|
缓存 前端开发 中间件
DDD 领域驱动设计落地实践系列:工程结构分层设计
前面几篇文章中,笔者给大家阐述了 DDD 领域驱动设计的三大过程,重点围绕如何通过战略设计与战术设计进行 DDD 落地实践进行了详细的讨论,但是还没有涉及到工程层面的落地。实际上所有的这些架构理论到最后都是为了使得我们代码结构更加清晰,从而开发出 bug 少、扩展性强、逻辑清楚的应用。因此本文就是为了解决 DDD 领域驱动落地实践最后一公里问题,将我们分析出来的领域模型通过与工程结构的映射实现真正的落地。
DDD 领域驱动设计落地实践系列:工程结构分层设计
|
设计模式 前端开发 关系型数据库
【DDD】全网最详细2万字讲解DDD,从理论到实战(代码示例) 3
【DDD】全网最详细2万字讲解DDD,从理论到实战(代码示例)
6013 2
|
设计模式 弹性计算 人工智能
阿里技术专家详解DDD系列 第四讲 - 领域层设计规范
在一个DDD架构设计中,领域层的设计合理性会直接影响整个架构的代码结构以及应用层、基础设施层的设计。但是领域层设计又是有挑战的任务,特别是在一个业务逻辑相对复杂应用中,每一个业务规则是应该放在Entity、ValueObject 还是 DomainService是值得用心思考的,既要避免未来的扩展性差,又要确保不会过度设计导致复杂性。
|
微服务 测试技术 Java
阿里技术专家详解 DDD 系列- Domain Primitive
关于DDD的一系列文章,希望能继续在总结前人的基础上发扬光大DDD的思想,但是通过一套我认为合理的代码结构、框架和约束,来降低DDD的实践门槛,提升代码质量、可测试性、安全性、健壮性。
62770 17
阿里技术专家详解 DDD 系列- Domain Primitive
|
SQL 缓存 Java
殷浩详解DDD系列 第三讲 - Repository模式
# 第三讲 - Repository模式 **写在前面** 这篇文章和上一篇隔了比较久,一方面是工作比较忙,另一方面是在讲Repository之前其实应该先讲Entity(实体)、Aggregate Root(聚合根)、Bounded Context(限界上下文)等概念。但在实际写的过程中,发现单纯讲Entity相关的东西会比较抽象,很难落地。所以本文被推倒重来,从Repository
38743 8
|
前端开发 架构师 搜索推荐
COLA 4.0:直击应用架构本质的最佳实践
COLA 4.0:直击应用架构本质的最佳实践
3824 0
COLA 4.0:直击应用架构本质的最佳实践
|
存储 设计模式 缓存
DDD领域驱动设计实战-分层架构及代码目录结构(下)
DDD领域驱动设计实战-分层架构及代码目录结构
2221 0
DDD领域驱动设计实战-分层架构及代码目录结构(下)
|
Java API 领域建模
领域驱动设计(DDD)-简单落地
一、序言     领域驱动设计是一种解决业务复杂性的设计思想,不是一种标准规则的解决方法。在本文中的实战示例可能会与常见的DDD规则方法不太一样,是简单、入门级别,新手可以快速实践版的DDD。如果不熟悉DDD设计思想可看下基础思想篇 二、设计阶段     领域建模设计阶段常见的方法有 四色建模法、EventSourcing等 推荐一篇博文正确理解领域建
12675 1
|
消息中间件 开发者
DDD领域驱动设计实战(六)-领域服务(上)
DDD领域驱动设计实战(六)-领域服务
798 0
DDD领域驱动设计实战(六)-领域服务(上)

热门文章

最新文章