共计 3466 个字符,预计需要花费 9 分钟才能阅读完成。
一、背景介绍
咱们团队始终在继续推动业务零碎的体系化治理工作,在这个过程中咱们积淀了本人的 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:最好是定义防腐层对象,短期可能升高一些开发效率,然而从长期和代码规范话的角度看,还是值得的。
作者:京东科技 史纪军
起源:京东云开发者社区 转载请注明起源