前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >从零到一搭建基础架构(3)-base模块搭建上篇

从零到一搭建基础架构(3)-base模块搭建上篇

作者头像
柏炎
发布2022-10-28 10:26:04
3760
发布2022-10-28 10:26:04
举报

本文为稀土掘金技术社区首发签约文章,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. 通用的常量散落在业务系统中,导致各个业务系统中重复的逻辑定义
  7. ...

我们需要将上述这些与业务本身无关,但又是辅助业务开发的工具性质的定义放在一个统一的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包呢?

  1. 业务无关性
  2. 工具类型
  3. 无需配置性

可以引入的示例

代码语言:javascript
复制
<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>

不建议引入的示例

代码语言:javascript
复制
<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将所有请求的日志串联起来,方便后续的日志定位。

综上我们应该定义规范的结构跟前端交互,让大家按照这个既定规范进行开发

普通接口响应结构

代码语言:javascript
复制
{
  "code": 200, // 业务code定义,区别于http code标识细分的业务请求结果,比如10000标识账号错误,10001标识密码错误
  "errorCode": null, // 发生错误时对于message的code,英文串描述,用于国际化异常映射
  "message": "请求成功", // code为200时返回请求成功,10000时返回账号错误,10001返回密码错误。发生异常时,依据errorCode的定义映射国际化请求响应
  "traceId": "", // 链路id,串联请求所关联所有应用的日志数据
  "data": null // 是的返回的业务数据
}

列表分页数据响应请求

代码语言:javascript
复制
{
  ..., // 与普通的一致
  "total": 100, // 查询条件下数据的总数
  "data": [] // 是的返回的业务数据,list结构
}

code实现

三、如何正确划分数据载体

实体类作为数据的载体,大家日常工作中绝对会接触到,但是你真的正确使用了吗?

说一下我之前项目中看到的代码。数据查询得到的数据载体,service层交互的数据载体,rpc层交互的数据载体,web层交互的数据载体都集中在一个实体中。这个做法在业务场景特别简单的时候不会出现什么大问题,但是如果是一个比较庞大的业务应用体系。这样就会就会有问题了。

举个例子

用户表中一个四个字段,用户id,用户名,手机号,用户密码。现在需要在用户管理菜单页展示用户数据。如果只有一个实体的情况下,我从数据库里查询出来的数据拥有4个字段,把密码传递到前端肯定是不合适的。做一下脱敏,将password置为空。但是你在前端的报文中还是能看到

代码语言:javascript
复制
{
  "userId":1
  "userName":"admin"
  "mobile":"13888888888"
  "password":null
}

显然这个是不合理的,返回给前端的数据应该是

代码语言:javascript
复制
{
  "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来表明当前实体是聚合根

代码语言:javascript
复制
/**
 * 聚合根标记
 *
 * @author baiyan
 */
public interface AggregateRoot extends MarkerInterface {
}

/**
 * 用户聚合根
 *
 * @author baiyan
 */
public class User implements AggregateRoot {
}

code演示

2.统一的分页查询参数

分页查询参数规范基本上就是两种:

  1. limit/offset
  2. pageSize/PageNo

为了兼容以上两种情况,我们设计一个顶级的父类,将上面两种参数都一一关联起来。

代码语言:javascript
复制
@Data
@EqualsAndHashCode(callSuper = true)
public class KeywordQuery extends PageQuery {

    @ApiModelProperty("关键字查询")
    private String keyword;
}

code演示

后续我们如果有分页需求的时候,只需要继承这个顶级的查询父类,只需要在查询条件内定义业务参数即可。

3.顶级的PO类设计

PO是持久化实体,与表结构的字段一一对应。我们在设计表结构数据时,抛开业务不管,应该是要有一些公共的字段的:id、创建时间、修改时间、删除标识(如果数据删除是使用软删除的方式)

代码语言:javascript
复制
@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模块中供业务使用。

代码语言:javascript
复制
@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职责。

五、联系我

如果你觉得文章写得不错,点赞评论+关注,么么哒~

微信:baiyan_lou

我的第一本掘金小册《深入浅出DDD》已经在掘金上线,欢迎大家试读~ DDD的微信群我也已经建好了,由于文章内不能放二维码,大家可以加我微信,备注DDD交流,我拉你进群,欢迎交流共同进步。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-10-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、Base包中Maven引入的规范
  • 二、定义统一的response body与biz code
  • 三、如何正确划分数据载体
  • 四、总结
  • 五、联系我
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档