本文为稀土技术社区首发签约文章,14天内制止转载,14天后未获授权制止转载,侵权必究!
Hello,这里是爱 Coding,爱 Hiphop,爱喝点小酒的 AKA 柏炎。
本篇是手把手建立根底架构专栏的第三篇。
第一篇:从零到一建立根底架构(1)-玩转maven依靠版别管理
第二篇:从零到一建立根底架构(2)-怎么构建根底架构模块区分
相信你们在日常开发的过程中必定遇到过以下的问题
- 前后端交互结构混乱,response中事务code界说没有一个一致的标准
- PO、DTO、BO、VO傻傻分不清楚
- 东西类众多,同一工程中StringUtil的引证有外部引进,有内部jar包引进还有自己界说的
- 反常界说混乱,导致在Spring一致response拦截的当地区别事务反常与code过错困难
- 通用性高的枚举重复界说,比如是否枚举,男女枚举
- 通用的常量散落在事务系统中,导致各个事务系统中重复的逻辑界说
- …
咱们需求将上述这些与事务自身无关,但又是辅助事务开发的东西性质的界说放在一个一致的base包内。
让事务系统聚焦于事务自身。
本文一切代码均在此link,可提前clone下来,假如遇到报错,请依照如下步骤操作
你需求先clone common-dependency
然后执行mvn clean install 将 common-dependency包打到你本地仓库
否则你拉下来common-frame工程后会报找不到
<parent> <groupId>com.baiyan</groupId> <artifactId>common-dependency</artifactId> <version>1.0.0-SNAPSHOT</version> </parent>
一、Base包中Maven引进的标准
在遵守第一篇与第二篇Maven依靠引进的标准的前提下,咱们在base包中能够引进什么样内部、外部的jar包呢?
- 事务无关性
- 东西类型
- 无需配置性
能够引进的示例
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
不主张引进的示例
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
二、界说一致的response body与biz code
前后端分离开发的时代,怎么做好前后端的数据交互呢?
我在有的工程中看过这样的后端接口:直接将前端所需求的数据回来,不做任何包装。
这么做会有什么问题呢?
咱们只能根据http code来响应后端的恳求成果。无法跟前端约好事务code,来让前端在UI上做特定的展现。
而http code自身是恳求级别的code界说,仅仅一个泛的界说。
比如登陆失败有很多种原因:账号不存在,暗码过错,账号确定等等。咱们假如只用http code 401告知前端登陆失败这明显用户交互非常不友好。
再比如,分布式架构系统下,一个恳求上涉及很多服务,咱们应该有一个一致的链路id将一切恳求的日志串联起来,便利后续的日志定位。
综上咱们应该界说标准的结构跟前端交互,让咱们依照这个既定标准进行开发
一般接口响应结构
{
"code": 200, // 事务code界说,区别于http code标识细分的事务恳求成果,比如10000标识账号过错,10001标识暗码过错
"errorCode": null, // 发生过错时关于message的code,英文串描绘,用于国际化反常映射
"message": "恳求成功", // code为200时回来恳求成功,10000时回来账号过错,10001回来暗码过错。发生反常时,根据errorCode的界说映射国际化恳求响应
"traceId": "", // 链路id,串联恳求所相关一切运用的日志数据
"data": null // 是的回来的事务数据
}
列表分页数据响应恳求
{
..., // 与一般的共同
"total": 100, // 查询条件下数据的总数
"data": [] // 是的回来的事务数据,list结构
}
code完成
三、怎么正确区分数据载体
实体类作为数据的载体,咱们日常工作中绝对会接触到,可是你真的正确运用了吗?
说一下我之前项目中看到的代码。数据查询得到的数据载体,service层交互的数据载体,rpc层交互的数据载体,web层交互的数据载体都会集在一个实体中。这个做法在事务场景特别简单的时分不会呈现什么大问题,可是假如是一个比较巨大的事务运用系统。这样就会就会有问题了。
举个比如
用户表中一个四个字段,用户id,用户名,手机号,用户暗码。现在需求在用户管理菜单页展现用户数据。假如只有一个实体的状况下,我从数据库里查询出来的数据拥有4个字段,把暗码传递到前端肯定是不合适的。做一下脱敏,将password置为空。可是你在前端的报文中仍是能看到
{
"userId":1
"userName":"admin"
"mobile":"13888888888"
"password":null
}
明显这个是不合理的,回来给前端的数据应该是
{
"userId":1
"userName":"admin"
"mobile":"13888888888"
}
明显关于java中的数据载体来说,每一层的分层是尤为重要的。我通常在会对数据载体做如下分层
实体类型 | 描绘 |
---|---|
PO | 耐久化目标,实体属性与表字段逐个对应,DAO层发生,在Service层被运用 |
BO | 事务目标,聚合PO层数据,也能够多表相关数据查询聚合,内部会有属性的事务逻辑处理办法。DAO/Service层发生,Service层运用 |
DTO | 数据传输目标,常用语service层,rpc层,controller层,用于数组传输的载体,内部无逻辑 |
VO | 数据展现层,用于controller层,这里我习气与办法的出参,用于切合DTO与VO层的结构差异 |
Query | 查询参数,controller层办法入参,接收前端的查询类型参数 |
Command | 指令性型参数,例如用户新增,用户修正的数据载体 |
阐明:
- DTO与VO我常常会混用,假如数据传输载体只会在controller展现层中被拼装运用,那直接回来给前端也能够,假如与前端要求不共同的状况,需求编写对应的Converter类进行处理,不能够将转换逻辑编写在DTO与VO中,他们仅仅数据载体。
- Command与DTO/VO,网上一些博主会将VO或者DTO作为web层入参进行数据的增删改。从结构化与界说上没有问题,可是这个跟数据载体带有指令就有点相关不上了。我对DTO与VO的理解是他们是成果型数据,是事务逻辑处理后的产物。而Command是指令性数据,经过Command类型参数,经由BO层事务逻辑,将数据映射到PO层与数据库交互。
- Query参数,与Command参数相似,常常有人会运用DTO或者VO来传递数据,相同的道理,事务语义不行强。
根据上面的标准咱们能够区分好事务系统内部的实体,关于这些事务实体他们又有那些共用的逻辑呢?
1.DDD结构区分
假如咱们的项目是DDD结构的分层,POJO需求有一个显示的标识符标明当时的POJO是什么左右,比如聚合根我会界说一个实体完成这个接口AggregateRoot来标明当时实体是聚合根
/**
* 聚合根符号
*
* @author baiyan
*/
public interface AggregateRoot extends MarkerInterface {
}
/**
* 用户聚合根
*
* @author baiyan
*/
public class User implements AggregateRoot {
}
code演示
2.一致的分页查询参数
分页查询参数标准基本上就是两种:
- limit/offset
- pageSize/PageNo
为了兼容以上两种状况,咱们规划一个尖端的父类,将上面两种参数都逐个相关起来。
@Data
@EqualsAndHashCode(callSuper = true)
public class KeywordQuery extends PageQuery {
@ApiModelProperty("关键字查询")
private String keyword;
}
code演示
后续咱们假如有分页需求的时分,只需求承继这个尖端的查询父类,只需求在查询条件内界说事务参数即可。
3.尖端的PO类规划
PO是耐久化实体,与表结构的字段逐个对应。咱们在规划表结构数据时,抛开事务不管,应该是要有一些公共的字段的:id、创立时刻、修正时刻、删去标识(假如数据删去是运用软删去的方法)
@Data
public class BaseUuidEntity {
/**
* 主键id 采用默许雪花算法
*/
@TableId
private Long id;
/**
* 创立时刻
*/
private LocalDateTime gmtCreate;
/**
* 修正时刻
*/
private LocalDateTime gmtModified;
/**
* 是否删去,0位未删去
*/
@TableLogic(delval = "current_timestamp()")
private Long deleted;
}
code演示
4.其他通用类型的mode抽取
再比如,咱们经常会回来给前端一些key/value结构的数据,这种结构是具有通用性,咱们能够将这种具有高通用的DTO也放在base模块中供事务运用。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DataDictDTO {
@ApiModelProperty("key值")
private String key;
@ApiModelProperty("value值")
private String value;
}
code演示
除了DTO以外,只需具有事务无关性与高可复用性的POJO的界说都能够避免在base模块中供事务运用。
四、总结
本篇是base包制造的上篇,从咱们在日常开发过程中可能会碰到的一些问题动身,为咱们介绍了base包在根底架构工程中的地位。
- 从事务无关性与与东西通用性的视点作为切入点,为咱们介绍了Maven依靠在base包中的运用。
- 从前后端协同开发一致语言视点,为咱们介绍了一致前后端数据结构的重要性与完成方法。
- 从单一POJO巨大后混乱的数据结构动身,为咱们介绍正确区分POJO责任。
五、联络我
假如你觉得文章写得不错,点赞谈论+重视,么么哒~
微信:baiyan_lou
我的第一本小册《浅显易懂DDD》已经在上线,欢迎咱们试读~
DDD的微信群我也已经建好了,由于文章内不能放二维码,咱们能够加我微信,补白DDD交流,我拉你进群,欢迎交流共同进步。