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