引言
Hello 大家好,这里是Anyin。
本来这周是打算写下MQTT的一些简单使用,因为前段时间做了一些和硬件打交道的工作,也算是做下总结。但是今天在写Anyin Cloud
项目的一个用户增删改查的时候,突然感觉写好一个CRUD
其实这里面还是有一些小道道的。
今天我们就来聊一聊,在日常开发工作中简单的CRUD
的一些小道道吧。
数据实体转换
在我们正常的三层架构下会有Controller
、Service
、Dao
层,他们的依赖是自顶向下的。如下:
我们进行代码分包的时候,我们一般都会规定上层是不能依赖下层;而层与层之间是需要进行数据交互的,所以产生了三个类型的基本数据实体:VO
、DTO
、DO
,他们分包对应的Controller
层、Service
层、Dao
层,转换顺序如下:
你会发现,这个过程需要转换的次数是4次。 例如一次查询并返回结果,那么的它的转换顺序如下:
ReqVO => ReqDTO => ReqDO
|
RespVO <= RespDTO <= RespDO
这个在实际的开发工程中,我会感觉特别烦,一次简单的查询竟然要操作6个实体类,4个转换方法。 所以,我对其做了一个减法:
- 去掉
RespVO
,RespDTO
和RespDO
都可以直接返回给前端,但是不能暴露表结构。 - 合并
ReqDO
和RespDO
,即它们就是DO
。 - 在某些场景下合并
ReqDTO
和RespDTO
。
这样子,我只剩下了:VO
、DTO
、DO
, 转换的次数一般都会是2次。
举个例子:用户列表查询,查询参数VO
转DTO
,DTO
转DO
, 查询结果DO
直接返回给前端。如下:
这里的
Form
就是VO
,Ext
就是DO
,Ext
它可能是一个单表的查询结果或者连表的查询结果,注意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);
对于业务对象或者实体类型的Controller
和Service
都会有以上5个基本方法,而关联对象或者非业务对象或者实体则不会有这5个基本方法。举个例子:用户、角色都是属于业务对象,都会有这5个基本方法,而用户和角色的关联关系表则不会有这5个基本方法,设置它都不会有对应的Controller
和Service类
。
create
和modify
方法他们的数据实体一般都是一样的,只是一个有id,一个没有id,所以不管在Controller
或者Service
层都会显示的的去传递这个id,这样子就能保证SysUserSaveForm
和 SysUserSaveDTO
数据实体的字段是一一对应的。
在Controller
层只会做2件事:
- 数据校验,这个都会通过在数据实体
Form
上的注解配合@Valid
注解进行处理。 - 数据转换,这里推荐使用
mapsturct
组件进行数据转换,会省事很多。
聚合的Service
层
在我理解的Service
层它应该是处理少量的简单业务逻辑;如果涉及某个复杂业务,则为该复杂业务单独新增一个ServiceA
类,由当前的Service
持有ServiceA
的引用,并且提供对应的get
方法。其他的业务Service
如果要引用ServiceA
则不能直接通过@Autowired
进行注入,而必须通过其主业务的Service
的getXXXService
来获取。
总而言之,在当前业务下,只有一个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