编码规范
- 1 架构规范
- 2 编码规范
- 2.1【推荐】配置,aop,工具类只需要在application、infrastructure、domain层定义。
- 2.2.Domain.Shared 正确用法
- 2.3 【推荐】infrastructure一级和二级目录
- 2.4 【推荐】跨微服务和跨系统调用规范
- 2.5 【推荐】多业务模块场景下dto和api定义
- 2.6【推荐】 一个文件夹下的class不超过20个(一屏),如果超过这个数量需要按业务模块分包。
- 2.7【推荐】 controller,service 目录要子域/业务模块组织代码
- 2.8 【强制】同一个流的操作不能操作10行代码
- 2.9 【强制】在内存处理的数据集(同一个数据对象)不能超过2000条
- 2.10 【强制】日志打印规范
- 2.11 数据库表设计规范
- 2.12 【强制】表JOIN约束
- 3 ORM框架使用规范
- 参考资料
1 架构规范
1.1 【强制】服务之间依赖顺序是单向的,避免循环依赖。
服务循环依赖的弊端
微服务之间的耦合性非常强,服务很难做到独立部署(支持平滑发版)。
如果发版,API协议发生变化,需要做部署规划,如果服务循环依赖,这种梳理的工作会耗费大量的时间。
循环依赖会让服务间的调用关系变得错综复杂,系统难于维护。造成一些复杂难以定位的问题.
系统的扩展性受到限制 系统扩展:假如大促我要升级扩容,如果正常不依赖其它应用,或者只依赖少量应用,只升级自己就可以了,如果依赖了其它应用,还得考虑其它应用的容量;还涉及到部署的情况,当你的业务做的很大,以前只是垂直应用,现在升级成平台型应用的时候,对部署有了更高的要求,可能要单独的隔离环境进行部署,如果依赖了不依赖的应用,会使这些几乎不可能。
业务扩展性收到限制,如果业务发展比较快,要支持更多的场景,那就必须考虑新的业务和这些依赖的应用是否兼容,增加研发成本,也不利于扩展。
要解决循环依赖,必须要在微服务之间建立一些原则来约束微服务之间的通信原则
定义服务上下游关系,下游服务可以直接依赖上游服务,反之则不可
上游服务的变更对下游服务产生影响需要通过领域事件(异步)的方式来实现
服务之间要通过数据Id(或类Id,能够唯一代表数据且不变的属性)来进行关联,尽量不做过多的数据冗余
一旦需要上游服务调用下游服务才能完成业务时,要考虑是否上游服务缺少业务概念
为满足前端逻辑而导致的服务间交互逻辑要放到BFF(Backend for frontend)中,而不是增加服务间的调用
1.2 [强制] 层的调用规范
禁止业务能力层调用BFF层
BFF层之间禁止调用
业务能力层之间可以允许调用,但是要避免循环依赖
1.3 [强制] API设计规范和升级规范
1 关注点分离,一个接口承担一个职责。 (满足设计模式 SRP:单一职责原则 和 接口隔离原则(Interface Segregation Principle, ISP))
反例:某个模块给一个大而全的接口给其他模块调用,该接口满足单个单据查询,批量查询,模糊查询等。对调用者使用造成不方便,而且大而全的并发下接口性能也是个隐患。
正确的做法:我们需要根据不同的场景设计接口
get 单个单据 性能最好(单张表),
getdetail 单个详情(业务单据 跨表的单条记录),
search (列表页面,各种组合场景)
下拉列表模糊查询 (比如根据sku名字模糊查询)
定制的业务场景的接口 -- 给某些模块定制的接口
2 完全穷尽,彼此独立,不应该提供相互叠加的 API。
3 版本化管理
4 体现兼容和不兼容的设计,如果不兼容需要拉通上下游,做好不兼容升级的沟通
兼容的接口升级
新增字段,必填字段需要设置缺省值;
原有字段扩展长度或新增字典值;
不兼容的接口升级
修改原有字段的语义及格式;
删除原有字段;
无法兼容时,应新增接口(接口升级版本);旧接口先保留,切过来再下旧接口
例子
原接口 http://localhost:8080/user/api/v1/get_user
由于接口协议变化大,无法兼容老接口,需要新增接口
新接口http://localhost:8080/user/api/v2/get_user
发版的时候,老先接口先保留,
可以按照以下的步骤进行版本迁移:
在低压力时间段,先升级一半(节点)提供者为新版本
再将所有消费者升级为新版本
然后将剩下的一半提供者升级为新版本
BFF接口升级,如果改动影响比较大,平滑升级方案。 新老接口并存,通过前端 身份配置新/老 接口做测试。 如果新接口发版失败,通过身份修改接口切换到老接口。
4【推荐】BFF 编码规范
1.4 [推荐] 分层架构规范(DDD,MVC 分层规范)
2 编码规范
2.1【推荐】配置,aop,工具类只需要在application、infrastructure、domain层定义。
目录规范
/common 一节目录
/config 配置类 二级目录
/ interceptor: 拦截器
/filter: 过滤器
/aspect: 自定义切面
/util:通用工具类,如StringUtils。(推荐每个产品将工具类统一使用)
错误用法:配置类只能在application或者infrastructure层。不应该在领域层和start模块。
2.2.Domain.Shared 正确用法
是工程中很薄的项目,它只包含共享的数据类型的定义.例如,枚举,常量等.
不能包含 config,utils。 由于api会依赖domain.shared,避免api模块引入复杂的二方库,
导致打出sdk给consumer调用,引起包冲突。
解决方式:参照【配置,aop,工具类只需要在application、infrastructure、domain层定义】
错误用法
2.3 【推荐】infrastructure一级和二级目录
/repository
/client
/dao
/mapper【mybaits的mapper】
/jdbc
/dataobject
/persist
/query【查询的入参】
/result【多表查询结果】
/converter
1、result和persist的区别,如果是单表查询,直接使用persist中的DO,如果是多表查询的结果,与persist中的DO有差距,则需要新建DTO放置在result中。
2、query,为查询入参
错误的用法
base-infrastructure 错误目录
order-service
2.4 【推荐】跨微服务和跨系统调用规范
【推荐】需要防腐处理,推荐跨系统的调用进行防腐处理,转换为内部需要的数据对象。
2.5 【推荐】多业务模块场景下dto和api定义
/业务模块
/dto
/command
/query
/response
/event
示例
2.6【推荐】 一个文件夹下的class不超过20个(一屏),如果超过这个数量需要按业务模块分包。
错误例子:一个文件夹下class太多,对类的寻找,管理很不方便
2.7【推荐】 controller,service 目录要子域/业务模块组织代码
示例
application/
order/ 中心或者微服务级
goods/ 微服务内的组件
client/ 跨微服务调用
service/
mktprototype/ 营销样机
service/
client/
presentation/
order/ 中心或者微服务级
goods/ 微服务内的组件
controller/
converter/
vo/
mktprototype/ 营销样机
controller/
converter/
vo/
错误的做法
2.8 【强制】同一个流的操作不能操作10行代码
反例:政策的某处代码,流式代码过于复杂,可读性很差。
2.9 【强制】在内存处理的数据集(同一个数据对象)不能超过2000条
分批处理大数据集:如果处理大量数据时,尽量采用分批处理的方式,而不是一次性加载全部数据到内存中。通过逐批处理数据,可以减少对内存的占用。重点关注定时任务,导入导出。
List<Data> dataList = // 获取大量数据集
int batchSize = 1000;
for (int i = 0; i < dataList.size(); i += batchSize) {
List<Data> batch = dataList.subList(i, Math.min(i + batchSize, dataList.size()));
// 处理当前批次的数据
// ...
// 当前批次处理完成后,及时释放该批次所占用的内存
batch.clear();
}
2.10 【强制】日志打印规范
最低要求:
打印堆栈跟踪:当记录错误或异常时,应该打印相关的堆栈跟踪信息,以便于问题追踪和排查。可以使用日志框架提供的特定方法,如logger.error("Error message", exception),来打印包含堆栈跟踪的异常信息。
Java开发 日志规约
2.11 数据库表设计规范
数据库 name 的长度定义规范
1 遵循源头,源头设置多长,下游使用也多长。源头长度发生改变需要通知下游
2 如果清晰知道 name的长度,自己定义,比如 color_name_cn ,用varchar(64) 是足够的
3 如果不能判断设置多少合适,定义varchar(512)
4 remark统一使用varchar(1024)
注意 !!超过10万的表需要谨慎使用过长的varchar,建索引时如果没有限制索引的大小,会按varchar长度占用内存空间,影响性能。 索引占用内存试算:varchar(1024)字段,10万数据量占用 内存数量粗略计算 100000*1k= 10M,关一个字段就占10M内存
2.12 【强制】表JOIN约束
AP类:导出,报表: 不超过4张表join。
TP: 列表查询,等。 不超过 3张表join
3 ORM框架使用规范
3.1【强制】新代码 mybatis的sql语句在xml维护。理由:生产慢查,容易通过慢查sql找到问题代码。
3.2 【推荐】Mybatis xml 多条件查询,必须命中查询条件
如果条件都没触发,会导致全量表加载到内存。
例:
<select id="findAllWithLine" resultMap="xxxxxx">
select co.*,
from xxx
<where>
co.id = cod.customer_order_id
<if test="brandCodes != null and brandCodes.size() > 0">
and co.brand_code in
<foreach collection="brandCodes" item="item" index="index" separator="," open="(" close=")">
#{item}
</foreach>
</if>
</where>
and deleted =1
</select>
改动方案
可选 1 在SQL后追加 and deleted =1 。 当没有命中任何条件的情况下,会报错。
可选2 在JAVA代码做控制,避免全量表数据加载到内存。
3.3 【强制】 新的表ORM框架只能使用Mybatis
3.4 【强制】 Mybatis插件 使用规范
禁止使用查询包装器(QueryWrapper)。
以下写法是不允许的
// 链式查询 普通
QueryChainWrapper
// 链式查询 lambda 式。注意:不支持 Kotlin
LambdaQueryChainWrapper
// 示例:
query().eq("column", value).one();
lambdaQuery().eq(Entity::getId, value).list();
不推荐使用ActiveRecord 模式,推荐使用 BaseMapper 继承
以下写法是不推荐的
推荐的写法
3.5 【强制】 后端新增接口提供给前端,需要走BFF
调用链路 前端 -> BFF -> 业务服务