2023- 手把手教你落地DDD
手把手教你落地DDD
1. 前言
常见的
本文将带领大家从日常的三层架构出发,精炼推导出我们自己的应用架构,并且将这个应用架构实现为
需要明确的是,本文只是给读者介绍了
2. 应用架构演化
我们很多项目是基于三层架构的,其结构如图:

我们说三层架构,为什么还画了一层
接下来我们开始对这个三层架构进行抽象精炼。
2.1 第一步、数据模型与DAO 层合并
为什么数据模型要与
首先,数据模型是贫血模型,数据模型中不包含业务逻辑,只作为装载模型属性的容器;
其次,数据模型与数据库表结构的字段是一一对应的,数据模型最主要的应用场景就是
最后,数据模型的
2.2 第二步、Service 层抽取业务逻辑
下面是一个常见的
public class Service {
@Transactional
public void bizLogic(Param param) {
checkParam(param);//校验不通过则抛出自定义的运行时异常
Data data = new Data();//或者是mapper.queryOne(param);
data.setId(param.getId());
if (condition1 == true) {
biz1 = biz1(param.getProperty1());
data.setProperty1(biz1);
} else {
biz1 = biz11(param.getProperty1());
data.setProperty1(biz1);
}
if (condition2 == true) {
biz2 = biz2(param.getProperty2());
data.setProperty2(biz2);
} else {
biz2 = biz22(param.getProperty2());
data.setProperty2(biz2);
}
//省略一堆set方法
mapper.updateXXXById(data);
}
}
这是典型的事务脚本的代码:先做参数校验,然后通过
由于所有的业务逻辑都在
专业的事情就该让专业的人干,既然业务逻辑是跟具体的业务场景相关的,我们想办法把业务逻辑提取出来,形成一个模型,让这个模型的对象去执行具体的业务逻辑。这样
将业务逻辑抽象成模型,这样的模型就是领域模型。
要操作领域模型,必须先获得领域模型,但此时我们先不管领域模型怎么得到,假设是通过loadDomain
方法获得的。通过loadDomain
方法得到一个模型,我们让这个模型去做业务逻辑,最后执行的结果也都在模型里,我们再将模型回写数据库。当然,怎么写数据库的我们也先不管,假设是通过saveDomain
方法。
public class Service {
public void bizLogic(Param param) {
//如果校验不通过,则抛一个运行时异常
checkParam(param);
//加载模型
Domain domain = loadDomain(param);
//调用外部服务取值
SomeValue someValue=this.getSomeValueFromOtherService(param.getProperty2());
//模型自己去做业务逻辑,Service不关心模型内部的业务规则
domain.doBusinessLogic(param.getProperty1(), someValue);
//保存模型
saveDomain(domain);
}
}
根据代码,我们已经将业务逻辑抽取出来了,领域相关的业务规则封闭在领域模型内部。此时
抽取完领域模型后,我们工程的结构如下图:

2.3 第三步、维护领域对象生命周期
在上一步中,loadDomain
、saveDomain
这两个方法还没有得到讨论,这两个方法跟领域对象的生命周期息息相关。
关于领域对象的生命周期的详细知识,读者可以自行学习了解。
不管是
保存或者加载领域模型,我们可以抽象成一种组件,通过这种组件进行封装模型加载、保存的操作,这种组件就是
注意,
以下是
public interface DomainRepository {
void save(AggregateRoot root);
AggregateRoot load(EntityId id);
}
接下来我们要考虑在哪里实现DomainRepository
。既然
但是,如果我们在
因此,我们决定在
这样调整之后,
现在,我们项目的架构图是这样的了:

由于数据模型属于贫血模型,自身没有业务逻辑,并且只有
Repository 这个包会用到,因此我们将之合并到Repository 中,接下来不再单独列举。
2.4 第四步、泛化抽象
在第三步中,我们的架构图已经跟经典四层架构非常相似了,我们再对某些层进行泛化抽象。
- Infrastructure
infrastructure-persistence
,可以理解为基础设施层持久化包。
之所以采取这种
例如:一般的项目,还有可能需要引入缓存,我们就可以再加一个包,名字叫infrastructure-cache
。
对于外部的调用,infrastructure-gateway
。
注意:
Infrastructure 层的门面接口都应先在Domain 层定义,其方法的入参、出参,都应该是领域模型(实体、值对象)或者基本类型。
- User Interface
同样,我们可能会有很多的用户接口,但是他们通过不同的协议对外提供服务,因而被划分到不同的包中。
我们如果有对外提供的ui-provider
。
有时候引入某个中间件会同时增加
例如,如果引入infrastructure-publisher
;如果是消费ui-subscriber
。
- Application
至此,
所以,我们把Application Service
层。
经过第四步的抽象,其架构图为:

2.5 第五步、完整的包结构
我们继续对第四步中出现的包进行整理,此时还需要考虑一个问题,我们的启动类应该放在哪里?
由于有很多的
加入启动包,我们就得到了完整的
包结构如图所示:

至此,
2.6 精炼后的思考
在经过前面五步精炼得到这个架构图中,经典四层架构的四层都出现了,而且长得跟六边形架构也很像。这是为什么呢?
其实,不管是经典四层架构、还是六边形架构,亦或者整洁架构,都是对系统应用的描述,也许描述的侧重点不一样,但是描述的是同一个事物。既然描述的是同一个事物,长得像才是理所当然的,不可能只是换一个描述方式,系统就从根本上发生了改变。
对于任何一个应用,都可以看成“输入
“输入”环节:通过某种协议对外暴露领域的能力,这些协议可能是
”处理“环节:处理环节是整个应用的核心,代表了应用具备的核心能力,是应用的价值所在,应用在这个环节执行业务逻辑,贫血模型由
“输出”环节,业务逻辑执行完成之后将结果输出到外部。
不管我们采用的什么架构,其描述的应用的核心都是这个过程,不必生搬硬套非得用什么应用架构。
正如《金刚经》所言:一切有为法,如梦幻泡影,如露亦如电,应作如是观;凡所有相,皆是虚妄;若见诸相非相,即见如来。
3. ddd-archetype
3.1 Maven Archetype 介绍
我们在介绍
我们使用
3.2 ddd-archetype 的使用
3.2.1 项目介绍
项目链接:
https://github.com/feiniaojin/ddd-archetype
3.2.2 安装过程
以下将以
克隆项目`-->`archetype:create-from-project`-->`install`-->`archetype:crawl
3.2.3 克隆项目
将项目克隆到本地:
git clone https://github.com/feiniaojin/ddd-archetype.git
直接使用主分支即可,然后使用

3.2.4 archetype:create-from-project
配置打开run/debug configurations
窗口,如下:!img
选择add new configurations
,弹出以下窗口:

其中,上图中
标识1
标识2
标识3
archetype:create-from-project -Darchetype.properties=archetype.properties
注意,在
IDEA 中添加的命令默认不需要加mvn
标识4
ddd-archetype
的根目录
以上配置完成后,点击执行该命令。
3.2.5 install
上一步执行完成且无报错之后,配置install
命令。

其中,上图中
标识1
install
;
标识2
ddd-archetype/target/generated-sources/archetype
install
配置完成之后,点击执行。
3.2.6 archetype:crawl
install
执行完成且无报错,接着配置archetype:crawl
命令。

其中,标识
archetype:crawl
配置完成,点击执行即可。
3.3 使用ddd-archetype 初始化项目
- 创建项目时,点击
manage catalogs
:!img - 将本地的
maven 私服中的archetype-catalog.xml
加入到catalogs 中:

添加成功,如下:!img
4. 代码案例
本文提供了配套的代码案例,该案例使用
4.1 后端
后端项目使用本文的ddd-archetype
创建,实现了部分

实现的
技术栈:
- Spring Boot
H2 内存数据库- Spring Data JDBC
无外部中间件依赖 ,
4.2 前端
前端项目基于vue-element-admin
开发,详细安装方式见代码库的

4.3 运行截图

5. 总结以及进一步学习
本文通过对贫血三层架构进行精炼,推导出适合我们落地的应用架构,并且将之实现为