本文为稀土技术社区首发签约文章,14天内制止转载,14天后未获授权制止转载,侵权必究!


Hello,这里是爱 Coding,爱 Hiphop,爱喝点小酒的 AKA 柏炎。

本篇是手把手建立根底架构专栏的第三篇。

第一篇:从零到一建立根底架构(1)-玩转maven依靠版别管理

第二篇:从零到一建立根底架构(2)-怎么构建根底架构模块区分

相信你们在日常开发的过程中必定遇到过以下的问题

  1. 前后端交互结构混乱,response中事务code界说没有一个一致的标准
  2. PO、DTO、BO、VO傻傻分不清楚
  3. 东西类众多,同一工程中StringUtil的引证有外部引进,有内部jar包引进还有自己界说的
  4. 反常界说混乱,导致在Spring一致response拦截的当地区别事务反常与code过错困难
  5. 通用性高的枚举重复界说,比如是否枚举男女枚举
  6. 通用的常量散落在事务系统中,导致各个事务系统中重复的逻辑界说

咱们需求将上述这些与事务自身无关,但又是辅助事务开发的东西性质的界说放在一个一致的base包内。

让事务系统聚焦于事务自身。

image.png

本文一切代码均在此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包呢?

  1. 事务无关性
  2. 东西类型
  3. 无需配置性

image.png

能够引进的示例

<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告知前端登陆失败这明显用户交互非常不友好。

image.png

再比如,分布式架构系统下,一个恳求上涉及很多服务,咱们应该有一个一致的链路id将一切恳求的日志串联起来,便利后续的日志定位

image.png

综上咱们应该界说标准的结构跟前端交互,让咱们依照这个既定标准进行开发

一般接口响应结构

{
  "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层交互的数据载体都会集在一个实体中。这个做法在事务场景特别简单的时分不会呈现什么大问题,可是假如是一个比较巨大的事务运用系统。这样就会就会有问题了。

举个比如

image-20210530145628068.png

用户表中一个四个字段,用户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. DTO与VO我常常会混用,假如数据传输载体只会在controller展现层中被拼装运用,那直接回来给前端也能够,假如与前端要求不共同的状况,需求编写对应的Converter类进行处理,不能够将转换逻辑编写在DTO与VO中,他们仅仅数据载体。
  2. Command与DTO/VO,网上一些博主会将VO或者DTO作为web层入参进行数据的增删改。从结构化与界说上没有问题,可是这个跟数据载体带有指令就有点相关不上了。我对DTO与VO的理解是他们是成果型数据,是事务逻辑处理后的产物。而Command是指令性数据,经过Command类型参数,经由BO层事务逻辑,将数据映射到PO层与数据库交互。
  3. 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.一致的分页查询参数

分页查询参数标准基本上就是两种:

  1. limit/offset
  2. 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包在根底架构工程中的地位。

  1. 从事务无关性与与东西通用性的视点作为切入点,为咱们介绍了Maven依靠在base包中的运用。
  2. 从前后端协同开发一致语言视点,为咱们介绍了一致前后端数据结构的重要性与完成方法。
  3. 从单一POJO巨大后混乱的数据结构动身,为咱们介绍正确区分POJO责任。

image.png

五、联络我

假如你觉得文章写得不错,点赞谈论+重视,么么哒~

微信:baiyan_lou

我的第一本小册《浅显易懂DDD》已经在上线,欢迎咱们试读~

DDD的微信群我也已经建好了,由于文章内不能放二维码,咱们能够加我微信,补白DDD交流,我拉你进群,欢迎交流共同进步。