Su的技术博客

  • 首页
  • 原创
  • 视频
  • Java
  • MySQL
  • DDD
  • 事故复盘
  • 架构方案
  • AI
  • Other
  • 工具
    • AI工具集
    • 工具清单
    • JSON在线格式化
    • JSON在线比较
    • SQL在线格式化
  • 打赏
  • 关于
路很长,又很短
  1. 首页
  2. DDD
  3. 正文
                           

【京东】手把手教你落地DDD

2023-06-29 5077点热度 0人点赞 0条评论

导读

本文将从日常的三层架构出发,精炼推导出自己的应用架构,并且将这个应用架构实现为Maven Archetype,最后使用Archetype创建一个简单的CMS项目作为本文的落地案例。

 

01 前言

在今年的敏捷团队建设中,我通过Suite执行器实现了一键自动化单元测试。Juint除了Suite执行器还有哪些执行器呢?由此我的Runner探索之旅开始了!
常见的DDD实现架构有很多种,如经典四层架构、六边形(适配器端口)架构、整洁架构(Clean Architecture)、CQRS架构等。架构无优劣高下之分,只要熟练掌握就都是合适的架构。本文不会逐个去讲解这些架构,感兴趣的读者可以自行去了解。

本文将带领大家从日常的三层架构出发,精炼推导出我们自己的应用架构,并且将这个应用架构实现为Maven Archetype,最后使用我们Archetype创建一个简单的CMS项目作为本文的落地案例。

需要明确的是,本文只是给读者介绍了DDD应用架构,还有许多概念没有涉及,例如实体、值对象、聚合、领域事件等,如果读者对完整落地DDD感兴趣,可以详细关注作者的开源项目《Thinking-in-DDD》,这个开源项目是笔者领域驱动设计实践的总结,GitHub链接为:https://github.com/feiniaojin/Thinking-in-DDD

 

02应用架构演化

很多项目是基于三层架构的,其结构如图:

手把手教你落地DDD图1.三层架构结构示意

既然说三层架构,为什么还画了一层 Model 呢?因为 Model 只是简单的 Java Bean,里面只有数据库表对应的属性,有的应用会将其单独拎出来作为一个Maven Module,但实际上可以合并到 DAO 层。

接下来开始对这个三层架构进行抽象精炼。

2.1 第一步 数据模型与DAO层合并

为什么数据模型要与DAO层合并呢?
首先,数据模型是贫血模型,数据模型中不包含业务逻辑,只作为装载模型属性的容器;
其次,数据模型与数据库表结构的字段是一一对应的,数据模型最主要的应用场景就是DAO层用来进行 ORM,给 Service 层返回封装好的数据模型,供Service 获取模型属性以执行业务;
最后,数据模型的 Class 或者属性字段上,通常带有 ORM 框架的一些注解,跟DAO层联系非常紧密,可以认为数据模型就是DAO层拿来查询或者持久化数据的,数据模型脱离了DAO层,意义不大。
2.2 第二步Service层抽取业务逻辑

下面是一个常见的 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);
    }
}
这是典型的事务脚本的代码:先做参数校验,然后通过 biz1、biz2 等子方法做业务,并将其结果通过一堆 Set 方法设置到数据模型中,再将数据模型更新到数据库。

由于所有的业务逻辑都在 Service 方法中,造成 Service 方法非常臃肿,Service 需要了解所有的业务规则,并且要清楚如何将基础设施串起来。同样的一条规则,例如if(condition1=true),很有可能在每个方法里面都出现。

专业的事情就该让专业的人干,既然业务逻辑是跟具体的业务场景相关的,那就想办法把业务逻辑提取出来,形成一个模型,让这个模型的对象去执行具体的业务逻辑。这样Service方法就不用再关心里面的 if/else 业务规则,只需要通过业务模型执行业务逻辑,并提供基础设施完成用例即可。

将业务逻辑抽象成模型,这样的模型就是领域模型。

要操作领域模型,必须先获得领域模型,但此时先不管领域模型怎么得到,假设是通过loadDomain方法获得的。通过 Service方法的入参,调用loadDomain方法得到一个模型,让这个模型去做业务逻辑,最后执行的结果也都在模型里,再将模型回写数据库。当然,怎么写数据库的也先不管,假设是通过saveDomain方法。

Service层的方法经过抽取之后,将得到如下的伪代码:

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);
    }
}

根据代码,这时已经将业务逻辑抽取出来了,领域相关的业务规则封闭在领域模型内部。此时 Service方法非常直观,就是获取模型、执行业务逻辑、保存模型,再协调基础设施完成其余的操作。

抽取完领域模型后工程的结构如下图:

手把手教你落地DDD图2.三层架构抽取业务模型后的架构图

2.3第三步维护领域对象生命周期

在上一步中,loadDomain、saveDomain这两个方法还没有得到讨论,这两个方法跟领域对象的生命周期息息相关。

关于领域对象的生命周期的详细知识,读者可以自行学习了解。

不管是 loadDomain 还是 saveDomain,一般都要依赖于数据库,所以这两个方法对应的逻辑,肯定是要跟 DAO 产生联系的。

保存或者加载领域模型,可以抽象成一种组件,通过这种组件进行封装模型加载、保存的操作,这种组件就是Repository。

注意,Repository 是对加载或者保存领域模型(这里指的是聚合根,因为只有聚合根才会有Repository)的抽象,必须对上层屏蔽领域模型持久化的细节,因此其方法的入参或者出参,一定是基本数据类型或者领域模型,不能是数据库表对应的数据模型。

以下是 Repository 的伪代码:

public interface DomainRepository {

    void save(AggregateRoot root);

    AggregateRoot load(EntityId id);
}
接下来要考虑在哪里实现DomainRepository。既然 DomainRepository 与底层数据库有关联,但是现在 DAO 层并没有引入 Domain 这个包,DAO 层自然无法提供 DomainRepository的实现,于是初步考虑是不是可以将 DomainRepository 实现在 Service 层。

但是,如果在 Service 中实现 DomainRepository,势必需要在 Service 层操作数据模型:查询出来数据模型再封装为领域模型、或者将领域模型转为数据模型再通过 ORM 保存,这个过程不该是 Service 层关心的。

因此,决定在 DAO 层直接引入 Domain 包,并在 DAO 层提供 DomainRepository 接口的实现,DAO 层查询出数据模型之后,封装成领域模型供DomainRepository 返回。

这样调整之后,DAO 层不再向 Service 返回数据模型,而是返回领域模型,这就隐藏了数据库交互的细节,也就把DAO层换个名字称之为Repository。

现在,项目的架构图是这样的了:
手把手教你落地DDD图3.三层架构演化第三步的架构图

由于数据模型属于贫血模型,自身没有业务逻辑,并且只有Repository这个包会用到,因此将之合并到Repository中,接下来不再单独列举。

2.4第四步泛化抽象

在第三步中,架构图已经跟经典四层架构非常相似了,再对某些层进行泛化抽象。

  • Infrastructure

Repository 仓储层其实属于基础设施层,只不过其职责是持久化和加载聚合,所以,将 Repository层改名为infrastructure-persistence,可以理解为基础设施层持久化包。

之所以采取这种 infrastructure-XXX 的格式进行命名,是由于 Infrastructure 可能会有很多的包,分别提供不同的基础设施支持。

例如:一般的项目,还有可能需要引入缓存,就可以再加一个包,名字叫infrastructure-cache。

对于外部的调用,DDD中有防腐层的概念,将外部模型通过防腐层进行隔离,避免污染本地上下文的领域模型。使用入口(Gateway)来封装对外部系统或资源的访问(详细见《企业应用架构模式》,18.1入口(Gateway)),因此将对外调用这一层称之为infrastructure-gateway。

注意:Infrastructure 层的门面接口都应先在Domain 层定义,其方法的入参、出参,都应该是领域模型(实体、值对象)或者基本类型。

  • User Interface

Controller 层其实就是用户接口层,即 User Interface 层,在项目简称 ui。当然了可能很多开发者会觉得叫UI好像很别扭,认为 UI 就是 UI 设计师设计的图形界面。

Controller 层的名字有很多,有的叫 Rest,有的叫 Resource,考虑到这一层不只是有 Rest 接口,还可能还有一系列 Web 相关的拦截器,所以一般也可以称之为 Web。因此,我们将其改名为 ui-web,即用户接口层的 Web 包。

同样,可能会有很多的用户接口,但是他们通过不同的协议对外提供服务,因而被划分到不同的包中。

如果有对外提供的 RPC服务,那么其服务实现类所在的包就可以命名为ui-provider。

有时候引入某个中间件会同时增加 Infrastructure 和 User Interface。

例如,如果引入 Kafka 就需要考虑一下,如果是给 Service 层提供调用的,例如逻辑执行完发送消息通知下游,那么就再加一个包infrastructure-publisher;如果是消费 Kafka 的消息,然后调用 Service 层执行业务逻辑的,那么就可以命名为ui-subscriber。

  • Application

至此,Service 层目前已经没有业务逻辑了,业务逻辑都在 Domain 层去执行了,Service 只是协调领域模型、基础设施层完成业务逻辑。

所以,把 Service 层改名为Application Service层。

经过第四步的抽象,其架构图为:
手把手教你落地DDD图4.三层架构演化第四步的架构图
2.5第五步 完整的包结构

继续对第四步中出现的包进行整理,此时还需要考虑一个问题,启动类应该放在哪里?

由于有很多的 User Interface,所以启动类放在任意一个User Interface中都不合适,放置在Application Service中也不合适,因此,启动类应该存放在单独的模块中。又因为 application这个名字被应用层占用了,所以将启动类所在的模块命名为 launcher,一个项目可以存在多个launcher,按需引用User Interface。

加入启动包,就得到了完整的 maven 包结构。

包结构如图所示:
手把手教你落地DDD图5.三层架构演化第五步的架构图
至此,DDD 项目的整体结构基本讲完了。
2.6精炼后的思考

在经过前面五步精炼得到这个架构图中,经典四层架构的四层都出现了,而且长得跟六边形架构也很像。这是为什么呢?

其实,不管是经典四层架构、还是六边形架构,亦或者整洁架构,都是对系统应用的描述,也许描述的侧重点不一样,但是描述的是同一个事物。既然描述的是同一个事物,长得像才是理所当然的,不可能只是换一个描述方式,系统就从根本上发生了改变。

对于任何一个应用,都可以看成“输入-处理-输出”的过程。

“输入”环节:通过某种协议对外暴露领域的能力,这些协议可能是 REST、可能是 RPC、可能是 MQ 的订阅者,也可能是 WebSocket,也可能是一些任务调度的 Task;

“处理”环节:处理环节是整个应用的核心,代表了应用具备的核心能力,是应用的价值所在,应用在这个环节执行业务逻辑,贫血模型由Service执行业务处理,充血模型则是由模型进行业务处理。

“输出”环节,业务逻辑执行完成之后将结果输出到外部。

不管采用的什么架构,其描述的应用的核心都是这个过程,不必生搬硬套非得用什么应用架构。

正如《金刚经》所言:一切有为法,如梦幻泡影,如露亦如电,应作如是观;凡所有相,皆是虚妄;若见诸相非相,即见如来。

03 ddd-archetype

3.1Maven Archetype介绍

Maven Archetype是一个Maven插件,可以帮助开发人员快速创建项目的基础结构,大大减少开发人员在创建项目时所需的时间和精力,并且可以确保项目结构的一致性和可重用性,从而提高代码质量和可维护性。

前文在介绍DDD应用架构时,对项目的结构进行了介绍。将项目分为多个Maven Module,如果每个项目都手工创建一次,是比较繁琐的工作,也不利项目结构的统一。

使用Maven Archetype创建DDD项目初始化的脚手架,使其在初始化时完整实现上文第五步的应用架构。

3.2ddd-archetype的使用

3.2.1 项目介绍

ddd-archetype是一个Maven Archetype的原型工程,将其克隆到本地之后,可以安装为Maven Archetype,帮助使用者快速创建DDD项目脚手架。
项目链接:https://github.com/feiniaojin/ddd-archetype

3.2.2 安装过程

以下将以IDEA为例展示ddd-archetype的安装使用过程,主要过程是:

克隆项目-->archetype:create-from-project-->install-->archetype:crawl

3.2.3 克隆项目

将项目克隆到本地:

git clone https://github.com/feiniaojin/ddd-archetype.git
直接使用主分支即可,然后使用IDEA打开该项目
手把手教你落地DDD图6.IDEA打开项目示意

3.2.4 archetype:create-from-project

配置打开IDEA的run/debug configurations窗口,如下:

手把手教你落地DDD图7.配置打开IDEA的run/debug configurations窗口示意

选择add new configurations,弹出以下窗口:
手把手教你落地DDD图8.选择add new configurations弹出窗口示意

其中,上图中1~4各个标识的值为:

标识1- 选择"+"号;

标识2- 选择"Maven";

标识3- 命令为:

archetype:create-from-project -Darchetype.properties=archetype.properties

注意,在IDEA中添加的命令默认不需要加mvn

标识4- 选择ddd-archetype的根目录
以上配置完成后,点击执行该命令。

3.2.5 install

上一步执行完成且无报错之后,配置install命令。

手把手教你落地DDD图9.配置install命令示意

其中,上图中1~2各个标识的值为:

标识1- 值为install;

标识2- 值为上一步运行的结果,路径为:
ddd-archetype/target/generated-sources/archetype
install配置完成之后,点击执行。

3.2.6 archetype:crawl

install执行完成且无报错,接着配置archetype:crawl命令。

手把手教你落地DDD图10.配置archetype:crawl命令示意
其中,标识1中的值为:
archetype:crawl

配置完成,点击执行即可。

3.3使用ddd-archetype初始化项目

  • 创建项目时,点击manage catalogs:
手把手教你落地DDD图11.创建项目时,点击manage catalogs
  • 将本地的maven私服中的archetype-catalog.xml加入到catalogs中:
手把手教你落地DDD图12.将本地的maven私服中的archetype-catalog.xml加入到catalogs中

添加成功,如下:

手把手教你落地DDD图13.添加成功示意

  • 创建项目时,选择本地archetype-catalog,并且选择ddd-archetype,填入项目信息并创建项目:
手把手教你落地DDD图14.创建项目时操作示意
  • 项目创建完成后:
手把手教你落地DDD图15.项目创建完成后示意

04 代码案例

本文提供了配套的代码案例,该案例使用DDD和本文的应用架构实现了简单的CMS系统。案例项目采用前后端分离的方式,因此有后端和前端两个代码库。

4.1后端

后端项目使用本文的ddd-archetype创建,实现了部分CMS的功能,并落地部分DDD的概念。

GitHub链接:https://github.com/feiniaojin/ddd-example-cms

手把手教你落地DDD

图16.后端项目使用本文的ddd-archetype创建

实现的DDD概念有:实体、值对象、聚合根、Factory、Repository、CQRS。

技术栈:
  • Spring Boot
  • H2内存数据库
  • Spring Data JDBC
无外部中间件依赖 ,clone到本地即可编译运行,非常方便。
4.2前端

前端项目基于vue-element-admin开发,详细安装方式见代码库的README。

GitHub链接:https://github.com/feiniaojin/ddd-example-cms-front

手把手教你落地DDD

图17.README中的详细安装方式

4.3运行截图

手把手教你落地DDD图18.运行截图

05 总结以及进一步学习

本文通过对贫血三层架构进行精炼,推导出适合落地的应用架构,并且将之实现为Maven Archetype以应用到实际开发。然而,应用架构只是落地DDD的基础,要完整落地DDD,读者还需要进一步掌握限界上下文、上下文映射、充血模型、实体、值对象、领域服务、Factory、Repository等知识点,希望本文能为读者抛砖引玉,打开学习领域驱动设计的大门。

手把手教你落地DDD
‍

 

本文仅供学习!所有权归属原作者。侵删!文章来源: 京东技术

更多文章:

  1. 从MVC到DDD,该如何下手重构?
  2. 京东平台研发朱志国:领域驱动设计(DDD)理论启示
  3. Go整洁架构实践
  4. 手把手教你实战TDD
  5. 殷浩详解DDD系列 第一讲 - Domain Primitive
  6. 殷浩详解DDD 第三讲 - Repository模式
  7. 应用分层架构最佳实践:Alibaba COLA 4.0
  8. 基于DDD的微服务设计和开发实战
  9. 大家一直在谈的领域驱动设计(DDD),我们在互联网业务系统是这么实践的
  10. 殷浩详解DDD 第四讲:领域层设计规范
标签: 京东 架构 Java ddd 领域驱动模型 领域驱动设计 脚手架
最后更新:2023-06-29

coder

分享干货文章,学习先进经验。

打赏 点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

广告
文章目录
  • 导读
  • 01 前言
  • 02应用架构演化
  • 03 ddd-archetype
  • 3.2.2 安装过程
  • 3.2.3 克隆项目
  • 3.2.4 archetype:create-from-project
  • 3.2.5 install
  • 3.2.6 archetype:crawl
  • 04 代码案例
  • 05 总结以及进一步学习
最新 热点 推荐
最新 热点 推荐
视频笔记:微服务架构P4 设计模式:每服务数据库、API 网关和事件驱动架构 干货 | 论Elasticsearch数据建模的重要性 马蜂窝消息总线——面向业务的消息服务设计 基于 MySQL Binlog 实现可配置的异构数据同步 视频笔记:Google发布Agent2Agent协议 视频笔记:什么是微服务,为什么是微服务? 视频笔记:什么是AI 智能体? 视频笔记:什么是Flink?
Elasticsearch 使用误区之六——富文本内容写入前不清洗基于 MySQL Binlog 实现可配置的异构数据同步马蜂窝消息总线——面向业务的消息服务设计视频笔记:微服务架构P4 设计模式:每服务数据库、API 网关和事件驱动架构干货 | 论Elasticsearch数据建模的重要性你可以不用RxJava,但必须得领悟它的思想!如何秒级实现接口间“幂等”补偿:一款轻量级仿幂等数据校正处理辅助工具视频笔记:什么是Flink?
JVM和机器规格调优在有赞的实践 线上问题处理案例1:出乎意料的数据库连接池 FSM-COLA无状态状态机 如何设计一款高性能分布式锁,实现数据的安全访问? Elasticsearch基础但非常有用的功能之一:别名 JVM GC配置指南 系统设计 | 设计和解析 DSL 分布式事务场景、概念和方案整理(含概念图)

CRUD (1) Event Sourcing (1) graphql (1) id (1) NoSQL (1) quarkus (1) rest (1) RocketMQ (2) Spring Boot (1) zk (1) zookeeper (1) 上下文 (1) 事务消息 (1) 二级缓存 (1) 值对象 (1) 关系数据库 (1) 分布式缓存 (1) 原子性 (1) 唯一ID (1) 商品 (1) 多对多 (1) 子域 (1) 字符集 (1) 客户端心跳 (1) 幂等 (2) 干货 (1) 并发 (1) 应用场景 (1) 应用架构图 (1) 康威定律 (2) 异步复制 (1) 微服务架构 (3) 总体方案 (1) 技术方案 (2) 技术架构 (2) 技术架构图 (1) 技能 (1) 持续集成 (1) 支撑域 (1) 故障恢复 (1) 数据架构图 (1) 方案选型 (1) 日记 (1) 服务发现 (1) 服务治理 (1) 服务注册 (2) 机房 (1) 核心域 (1) 泄漏 (1) 洋葱架构 (1) 消息队列 (5) 源码剖析 (1) 灰度发布 (1) 熔断 (1) 生态 (1) 画图工具 (1) 研发团队 (1) 线程 (2) 组织架构 (1) 缓存架构 (1) 编码 (1) 视频 (19) 读写分离 (1) 贵州 (1) 软件设计 (1) 迁移 (1) 通用域 (1) 集群化 (1) 雪花算法 (1) 顺序消息 (1)

推荐链接🔗
  • AI工具集
  • 工具箱🛠️

COPYRIGHT © 2014-2025 verysu.com . ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

粤ICP备15033072号-2

x