关于简单CRUD的一些思考

863 阅读4分钟

引言

Hello 大家好,这里是Anyin。

本来这周是打算写下MQTT的一些简单使用,因为前段时间做了一些和硬件打交道的工作,也算是做下总结。但是今天在写Anyin Cloud项目的一个用户增删改查的时候,突然感觉写好一个CRUD 其实这里面还是有一些小道道的。

今天我们就来聊一聊,在日常开发工作中简单的CRUD的一些小道道吧。

数据实体转换

在我们正常的三层架构下会有ControllerServiceDao层,他们的依赖是自顶向下的。如下:

image.png

我们进行代码分包的时候,我们一般都会规定上层是不能依赖下层;而层与层之间是需要进行数据交互的,所以产生了三个类型的基本数据实体:VODTODO,他们分包对应的Controller层、Service层、Dao层,转换顺序如下:

image.png

你会发现,这个过程需要转换的次数是4次。 例如一次查询并返回结果,那么的它的转换顺序如下:

ReqVO  =>  ReqDTO  =>  ReqDO
                         |
RespVO <=  RespDTO <=  RespDO

这个在实际的开发工程中,我会感觉特别烦,一次简单的查询竟然要操作6个实体类,4个转换方法。 所以,我对其做了一个减法:

  1. 去掉RespVORespDTORespDO都可以直接返回给前端,但是不能暴露表结构
  2. 合并ReqDORespDO,即它们就是DO
  3. 在某些场景下合并ReqDTORespDTO

这样子,我只剩下了:VODTODO, 转换的次数一般都会是2次。
举个例子:用户列表查询,查询参数VODTODTODO, 查询结果DO直接返回给前端。如下:

image.png

image.png

这里的Form就是VOExt就是DOExt它可能是一个单表的查询结果或者连表的查询结果,注意Ext不能直接继承数据映射的DO,否则这里返回会有暴露表结构的风险

这样,整个数据实体转换起码写起来就简单许多,虽然也是无脑的CURD

方法命名和方法签名

这里说的方法命令和方法签名指的是CRUD的方法命名和签名,Controller层和Service层会保持对应。一般我习惯使用以下5种形式:

新增

// --------- 新增 ---------
// Controlelr层
public ApiResponse create(@Valid  @RequestBody SysUserSaveForm form)
// Service层
public void create(SysUserSaveDTO info);

// --------- 编辑 ---------
public ApiResponse modify(@PathVariable("id") Long id, @Valid  @RequestBody SysUserSaveForm form)
public void modify(Long id, SysUserSaveDTO info);

// --------- 查询 ---------
public ApiResponse<SysUserInfoDTO> info(@PathVariable("id") Long id)
public SysUserInfoDTO info(Long id);

// --------- 删除 ---------
public ApiResponse delete(@PathVariable("id") Long id)
public void delete(Long id);

// --------- 分页 ---------
public ApiResponse<Page<SysUserExt>> page(@RequestBody SysUserPageForm form)
public Page<SysUserExt> page(SysUserPageDTO query);

对于业务对象或者实体类型的ControllerService都会有以上5个基本方法,而关联对象或者非业务对象或者实体则不会有这5个基本方法。举个例子:用户、角色都是属于业务对象,都会有这5个基本方法,而用户和角色的关联关系表则不会有这5个基本方法,设置它都不会有对应的ControllerService类

createmodify方法他们的数据实体一般都是一样的,只是一个有id,一个没有id,所以不管在Controller或者Service层都会显示的的去传递这个id,这样子就能保证SysUserSaveFormSysUserSaveDTO 数据实体的字段是一一对应的。

Controller层只会做2件事:

  1. 数据校验,这个都会通过在数据实体Form上的注解配合@Valid注解进行处理。
  2. 数据转换,这里推荐使用mapsturct组件进行数据转换,会省事很多。

聚合的Service

在我理解的Service层它应该是处理少量的简单业务逻辑;如果涉及某个复杂业务,则为该复杂业务单独新增一个ServiceA类,由当前的Service持有ServiceA的引用,并且提供对应的get方法。其他的业务Service如果要引用ServiceA则不能直接通过@Autowired进行注入,而必须通过其主业务的ServicegetXXXService来获取。

总而言之,在当前业务下,只有一个Service类会对外提供服务,其子业务通过getXXXService来获取。是不是有DDD那套关于聚合根的那味了?

Service层还会持有一个到多个的Repository的引用。Service层关于复杂的业务逻辑会剥离到其他的子Service服务中,而关于数据库相关的操作则剥离到Repository当中。举个例子:

SysUserService会持有SysUserRepository的引用,并提供getRepository方法来获取SysUserRepository的实例。SysUserService类中除了上一节的5个基本方法,涉及其他的简单或者复杂的数据库操作都应放在SysUserRepository当中,例如getByUsername这种应该放在SysUserRepository中。 另外,SysUserService可能会持有其关联表的Repository,例如用户和角色的关联表SysUserRoleRepository

Service是其子业务Service和关联的Repository聚合,它会提供的对应的getXXX方法,其他业务的Service不能直接引用别的业务的子业务Service和关联的Repository

通过以上表述的一些规范,这样子我们的Service就不再臃肿,而只是一个负责调度或者编排的工具人了。

最后

以上,就是今天在写简单的CRUD的一些简单的思考。如果有什么建议,欢迎指正。

相关源码:Anyin Cloud