我们需要将上述这些与业务本身无关,但又是辅助业务开发的工具性质的定义放在一个统一的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> 复制代码
在遵守第一篇与第二篇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>
- 复制代码
前后端分离开发的时代,如何做好前后端的数据交互呢?
我在有的工程中看过这样的后端接口:直接将前端所需要的数据返回,不做任何包装。
这么做会有什么问题呢?
我们只能依据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结构
- }
- 复制代码
实体类作为数据的载体,大家日常工作中绝对会接触到,但是你真的正确使用了吗?
说一下我之前项目中看到的代码。数据查询得到的数据载体,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 | 指令性型参数,例如用户新增,用户修改的数据载体 |
说明:
依据上面的规范我们能够划分好业务系统内部的实体,对于这些业务实体他们又有那些公用的逻辑呢?
1.DDD结构划分
如果我们的项目是DDD结构的分层,POJO需要有一个显示的标识符表明当前的POJO是什么左右,比如聚合根我会定义一个实体实现这个接口AggregateRoot来表明当前实体是聚合根
- /**
- * 聚合根标记
- *
- * @author baiyan
- */
- public interface AggregateRoot extends MarkerInterface {
- }
-
- /**
- * 用户聚合根
- *
- * @author baiyan
- */
- public class User implements AggregateRoot {
- }
- 复制代码
2.统一的分页查询参数
分页查询参数规范基本上就是两种:
为了兼容以上两种情况,我们设计一个顶级的父类,将上面两种参数都一一关联起来。
- @Data
- @EqualsAndHashCode(callSuper = true)
- public class KeywordQuery extends PageQuery {
-
- @ApiModelProperty("关键字查询")
- private String keyword;
- }
- 复制代码
后续我们如果有分页需求的时候,只需要继承这个顶级的查询父类,只需要在查询条件内定义业务参数即可。
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;
-
- }
- 复制代码
4.其他通用类型的mode抽取
再比如,我们经常会返回给前端一些key/value结构的数据,这种结构是具备通用性,我们可以将这种具备高通用的DTO也放在base模块中供业务使用。
- @Data
- @NoArgsConstructor
- @AllArgsConstructor
- public class DataDictDTO {
-
- @ApiModelProperty("key值")
- private String key;
-
- @ApiModelProperty("value值")
- private String value;
-
- }
- 复制代码
除了DTO以外,只要具备业务无关性与高可复用性的POJO的定义都可以防止在base模块中供业务使用。
本篇是base包制作的上篇,从大家在日常开发过程中可能会碰到的一些问题出发,为大家介绍了base包在基础架构工程中的地位。
