1. 前言

常见的DDD完结架构有许多种,如经典四层架构、六边形(适配器端口)架构、整齐架构(Clean Architecture)、CQRS架构等。架构无好坏高低之分,只要熟练把握就都是适宜的架构。本文不会逐一去解说这些架构,感兴趣的读者能够自行去了解。

本文将带领咱们从日常的三层架构出发,精粹推导出咱们自己的运用架构,而且将这个运用架构完结为Maven Archetype,最终运用咱们Archetype创立一个简略的CMS项目作为本文的落地事例。

需求明确的是,本文只是给读者介绍了DDD运用架构,还有许多概念没有涉及,例如实体、值目标、聚合、范畴作业等,假如读者对完好落地DDD感兴趣,能够到本文最终了解更多。

2. 运用架构演化

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

【实践篇】手把手教你落地DDD | 京东云技术团队

咱们说三层架构,为什么还画了一层 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.3 第三步、保护范畴目标生命周期

在上一步中,loadDomainsaveDomain这两个办法还没有得到评论,这两个办法跟范畴目标的生命周期休戚相关。

关于范畴目标的生命周期的具体常识,读者能够自行学习了解。

不论是 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 | 京东云技术团队

因为数据模型归于贫血模型,自身没有事务逻辑,而且只有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 | 京东云技术团队

2.5 第五步、完好的包结构

咱们继续对第四步中呈现的包进行收拾,此刻还需求考虑一个问题,咱们的发动类应该放在哪里?

因为有许多的 User Interface,所以发动类放在任意一个User Interface中都不适宜,放置在Application Service中也不适宜,因此,发动类应该存放在独自的模块中。又因为 application这个名字被运用层占用了,所以将发动类地点的模块命名为 launcher,一个项目能够存在多个launcher,按需引证User Interface。

加入发动包,咱们就得到了完好的 maven 包结构。

包结构如图所示:

【实践篇】手把手教你落地DDD | 京东云技术团队

至此,DDD 项目的全体结构根本讲完了。

2.6 精粹后的考虑

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

其实,不论是经典四层架构、仍是六边形架构,亦或许整齐架构,都是对体系运用的描绘,或许描绘的侧重点不一样,可是描绘的是同一个事物。既然描绘的是同一个事物,长得像才是天经地义的,不或许只是换一个描绘方法,体系就从根本上发生了改变。

关于任何一个运用,都能够当作“输入-处理-输出”的进程。

“输入”环节:经过某种协议对外暴露范畴的才能,这些协议或许是 REST、或许是 RPC、或许是 MQ 的订阅者,也或许是WebSocket,也或许是一些使命调度的 Task;

”处理“环节:处理环节是整个运用的中心,代表了运用具备的中心才能,是运用的价值地点,运用在这个环节履行事务逻辑,贫血模型由Service履行事务处理,充血模型则是由模型进行事务处理。

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

不论咱们采用的什么架构,其描绘的运用的中心都是这个进程,不用生搬硬套非得用什么运用架构。

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

3. ddd-archetype

3.1 Maven Archetype介绍

Maven Archetype是一个Maven插件,能够帮助开发人员快速创立项目的根底结构,大大减少开发人员在创立项目时所需的时刻和精力,而且能够确保项目结构的一致性和可重用性,然后提高代码质量和可保护性。

咱们在介绍DDD运用架构时,对项目的结构进行了介绍。咱们将项目分为多个Maven Module,假如每个项目都手工创立一次,是比较繁琐的作业,也晦气项目结构的一致。

咱们运用Maven Archetype创立DDD项目初始化的脚手架,使其在初始化时完好完结上文第五步的运用架构。

3.2 ddd-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 | 京东云技术团队

3.2.4 archetype:create-from-project

装备打开IDEA的run/debug configurations窗口,如下:

【实践篇】手把手教你落地DDD | 京东云技术团队

挑选add new configurations,弹出以下窗口:

【实践篇】手把手教你落地DDD | 京东云技术团队

其中,上图中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 | 京东云技术团队

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

标识1– 值为install

标识2– 值为上一步运转的成果,路径为:

ddd-archetype/target/generated-sources/archetype

install装备完结之后,点击履行。

3.2.6 archetype:crawl

install履行完结且无报错,接着装备archetype:crawl指令。

【实践篇】手把手教你落地DDD | 京东云技术团队

其中,标识1中的值为:

archetype:crawl

装备完结,点击履行即可。

3.3 运用ddd-archetype初始化项目

  • 创立项目时,点击manage catalogs
    【实践篇】手把手教你落地DDD | 京东云技术团队
  • 将本地的maven私服中的archetype-catalog.xml加入到catalogs中:

【实践篇】手把手教你落地DDD | 京东云技术团队

添加成功,如下:

【实践篇】手把手教你落地DDD | 京东云技术团队

  • 创立项目时,挑选本地archetype-catalog,而且挑选ddd-archetype,填入项目信息并创立项目:
    【实践篇】手把手教你落地DDD | 京东云技术团队
  • 项目创立完结后:
    【实践篇】手把手教你落地DDD | 京东云技术团队

4. 代码事例

本文供给了配套的代码事例,该事例运用DDD和本文的运用架构完结了简略的CMS体系。事例项目采用前后端分离的方法,因此有后端和前端两个代码库。

4.1 后端

后端项目运用本文的ddd-archetype创立,完结了部分CMS的功能,并落地部分DDD的概念。

GitHub链接:github.com/feiniaojin/…

【实践篇】手把手教你落地DDD | 京东云技术团队

完结的DDD概念有:实体、值目标、聚合根、Factory、Repository、CQRS。

技术栈:

  • Spring Boot
  • H2内存数据库
  • Spring Data JDBC

无外部中间件依赖 ,clone到本地即可编译运转,非常方便。

4.2 前端

前端项目基于vue-element-admin开发,具体装置方法见代码库的README。

GitHub链接:github.com/feiniaojin/…

【实践篇】手把手教你落地DDD | 京东云技术团队

4.3 运转截图

【实践篇】手把手教你落地DDD | 京东云技术团队

5. 总结以及进一步学习

本文经过对贫血三层架构进行精粹,推导出合适咱们落地的运用架构,而且将之完结为Maven Archetype以运用到实际开发,然而运用架构只是落地DDD的一个常识点,要完好落地DDD还必须体系化地把握限界上下文、上下文映射、充血模型、实体、值目标、范畴服务、Factory、Repository等常识点。

作者:京东物流 覃玉杰

内容来源:京东云开发者社区