乐趣区

关于ddd:实践篇DDD脚手架及编码规范-京东云技术团队

一、背景介绍

咱们团队始终在继续推动业务零碎的体系化治理工作,在这个过程中咱们积淀了本人的 DDD 脚手架我的项目。脚手架我的项目是体系化治理过程中比拟重要的一环,它的作用有两点:

(1)能够对新建的我的项目进行对立的标准;

(2)对于领导老我的项目进行 DDD 的革新提供领导。

本文次要是梳理和总结了 DDD 脚手架应用中的编码标准以及遇到的问题。

二、脚手架的实践根底

DDD 相干的利用架构有很多种,比方四层架构,洋葱架构,六边形架构,整洁架构等。这些利用架构都有各自的特点和不同。然而他们的总体思维都是类似的,次要是通过分层来实现性能和关注点的隔离。达到的指标是畛域层不依赖任何其余内部实现,这样就能保障外围业务逻辑的洁净和稳固。

左图是整洁架构的示意图,左图为分层,右图示意各个分层的变动频率和形象层级。整洁架构次要分为 4 层:

(1)Frameworks&Drivers 层 :这一层示意零碎依赖的内部零碎,比方数据库、缓存、前端页面等。这一层是变动频率最高的,也是须要和咱们的外围业务逻辑做隔离的。

(2)Interface Adapters 层 :这一层是一个适配层,次要负责内部零碎和外部业务零碎的适配,这一层的次要作用就是内部零碎和外部零碎的适配和协定转换。

(3)Application Business Rules: 利用业务规定层,能够了解为用例层,这一层示意整个利用能够提供哪些用例级别的性能和服务。这一层也是对第 4 层中的外围业务规定的编排层。

(4)Enterprise Business Rules: 这一层就是最为外围的业务逻辑层,这一层不蕴含任何和技术相干的内容,只蕴含业务逻辑。

三、脚手架介绍及应用

应用命令如下:

mvn archetype:generate     
-DarchetypeGroupId=com.jd.jr.cf     
-DarchetypeArtifactId=ddd-archetype     
-DarchetypeCatalog=local     
-DarchetypeVersion=0.0.1-SNAPSHOT     
-DinteractiveMode=false     
-DgroupId=com.jd.demo.test        // 从这一行开始须要依据项目名称批改 
-DartifactId=demo-test     
-Dversion=1.0.0     
-Dpackage=com.jd.demo.test     
-DappName=demo-test  -s D:/git/settings.xml      // 本地 git 配置文件


生成完的我的项目构造如下:

|--- adapter                     -- 适配器层 利用与内部利用交互适配
|      |--- controller           -- 控制器层,API 中的接口的实现
|      |       |--- assembler    -- 拆卸器,DTO 和畛域模型的转换
|      |       |--- impl         -- 协定层中接口的实现
|      |--- repository           -- 仓储层
|      |       |--- assembler    -- 拆卸器,PO 和畛域模型的转换
|      |       |--- impl         -- 畛域层中仓储接口的实现
|      |--- rpc                  -- RPC 层,Domain 层中 port 中依赖的内部的接口实现,调用近程 RPC 接口
|      |--- task                 -- 工作,次要是调度工作的适配器
|--- api                         -- 利用协定层 利用对外裸露的 api 接口
|--- boot                        -- 启动层 利用框架、驱动等
|      |--- aop                  -- 切面
|      |--- config               -- 配置
|      |--- Application          -- 启动类
|--- app                         -- 应用层
|      |--- cases                -- 应用服务
|--- domain                      -- 畛域层
|      |--- model                -- 畛域对象
|      |       |--- aggregate    -- 聚合
|      |       |--- entities     -- 实休
|      |       |--- vo           -- 值对象
|      |--- service              -- 域服务
|      |--- factory              -- 工厂,针对一些简单的 Object 能够通过工厂来构建
|      |--- port                 -- 端口,即接口
|      |--- event                -- 畛域事件
|      |--- exception            -- 异样封装
|      |--- ability              -- 畛域能力
|      |--- extension            -- 扩大点
|      |       |--- impl        -- 扩大点实现
|--- query                       -- 查问层,封装读服务
|      |--- model                -- 查问模型
|      |--- service              -- 查问服务


整体的分层架构图如下:

四、脚手架编码标准

1、Api 模块编码标准:

  • Api 模块是专门用于定义对外接口的模块,所以这个模块中只蕴含接口定义,出入参定义,尽量不依赖其余包。
  • Api 中的接口定义类以 xxxxResource(或者 xxxxService) 结尾。这条标准齐全是为了和老的利用保持一致。
  • Api 接口的入参尽量不要应用 Java 中的原子类型 (Primitive Type),须要将入参定义为独自的类。最好是继承现有的 BaseRequest 类。
  • Api 接口的出参对立应用泛型类对实在的返回类型进行包装。
  • 出入参类都以 DTO 结尾。
  • 出入参中尽量不实用枚举值类型的成员变量。

2、Adapter/Controller 模块编码标准:

  • 这一层中须要将出入参的 DTO 和业务层的 VO/DO 对象进行转换。
  • 这一层不要蕴含任何的业务逻辑,只蕴含参数转换和业务无关的校验逻辑。
  • 接口返回值缓存类的逻辑,能够放在这个模块中实现,因为这个动作不蕴含业务逻辑。

3、App 模块编码标准:

  • 这个模块中的类对立以 Case 结尾。
  • 这一层次要是对底层业务逻辑进行编排。能够间接调用 Domain 层的 port 定义。跨域的服务调用也能够放在这个模块中。
  • 这一层能够间接调用 Domain 模块中定义的 Repository 服务。
  • 事务处理:如果是跨多个聚合的业务逻辑须要放在一个事务中,须要在这一层开启和提交事务。

4、Domain 层编码标准:

  • DomainService 命名对立以 Service 为后缀。
  • Entity 实体类的命名不必后缀。值对象类的定义对立以 VO 结尾。
  • DomainService 逻辑中能够调用 Repository 和 Port 中定义的接口。
  • DomainService 能够操作多个聚合,实体和值对象。
  • Entity 实体类能够有构造函数,builder,getters。不要间接放开所有属性的 setters,避免业务代码随便批改实体的属性。
  • 编写业务逻辑须要恪守准则:优先将业务逻辑放在 Entity 和 VO 中,而后才是放在聚合中,最初才放在 DomainService 中。
  • 依赖反转准则:Domain 层依赖的内部接口都要定义在 Domain 模块的 port 包中。Domain 层只面向接口编程,不依赖接口实现类。

5、Adapter/Repository 和 Rpc 模块编码标准:

  • Repository 实现类中须要将接口入参中的 DO 对象转换为 PO 对象后再调用数据库存储。
  • Repository 和聚合的关系是一对一的关系。一个 Repository 有惟一的对应的聚合。
  • 如果 Repository 中须要开始事务能够在 Repository 实现类中开启事务。
  • Rpc 层最好是对外部接口的出参和入参定义一个防腐层对象,命名对立以 DTO 结尾。

五、常见问题及解决办法

Q1、Api 模块对外提供的 jar 包中是否要援用其余利用的 jar 包?

A1:有一些场景,A 利用的 Api 接口的入参须要援用其余利用的包中的类。比方 A 利用收回了一个事件,B 利用提供了一个接口来解决这个事件,那 B 利用是否要援用 A 利用的包中的事件定义类呢?现实状况,最好是 B 利用定义一个本人的类,这样 B 利用就不会依赖 A 利用的包。

Q2、Api 包中是否能蕴含枚举类的定义?

A2:最好不要在 Api 包中对外裸露外部的枚举值定义。因为枚举值是须要在 Domain 模块中定义和应用的,不适宜通过 jar 包的模式裸露给内部。如果的确有需要要裸露给内部利用 (比方为了让接口调用方不便的晓得入参中的值有哪些),能够将枚举类的定义放在同一的 common 包中。这样 Domain 模块和对外提供的 jar 包都能够援用 common 包。

Q3、数据存储是否要应用对立版本号?

A3:对于新利用,最好是应用对立的版本号,这样在更新数据库的时候就能够对立应用版本号当做乐观锁。然而对于遗留零碎而言,启用版本号的老本比拟高,因为须要梳理所有对实体进行变更的点,要求所有的点都对立应用版本号。所以要依据状况来确定是否应用。

Q4、对于一些偏流程性的业务,频繁的调用内部 rpc 接口。如果每个 rpc 接口都增加一个防腐层对象的话,会升高开发效率。是否能够不定义防腐层对象?

A4:最好是定义防腐层对象,短期可能升高一些开发效率,然而从长期和代码规范话的角度看,还是值得的。

作者:京东科技 史纪军

起源:京东云开发者社区 转载请注明起源

退出移动版