优秀的代码千篇一律,恶心的代码升职加薪
术语
分层领域模型规约:
- POJO(Plain Ordinary Java Object): 在本规约中,POJO 专指只有 setter/getter/toString 的 简单类,包括 DO/DTO/BO/VO 等。
- DO(Data Object):此对象与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。
- DTO(Data Transfer Object):数据传输对象,Service 或 Manager 向外传输的对象。
- BO(Business Object):业务对象,可以由 Service 层输出的封装业务逻辑的对象。
- Query:数据查询对象,各层接收上层的查询请求。注意超过 2 个参数的查询封装,禁止使用 Map 类 来传输。
- VO(View Object):显示层对象,通常是 Web 向模板渲染引擎层传输的对象。
序
以上是《阿里巴巴Java开发手册》中关于领域模型的规约。不得不说,手册中对于这一块的分层还是比较细致的。每一个层都有自己对应的领域模型。
问题
但是真正的用到实践中还是有点问题的,当然也可能是我们的项目不够大,所以才会觉得有问题。追求每一层都用自己的领域模型,就会导致在一次请求中一个对象可能会出现3次甚至4次转换,当返回的时候同样也会出现3-4次转换。
当然转换什么的,其实也容易,最让我难受的是这些领域对象的定义。
比如DO是数据库的对象,与数据库表结构一一对应。但是那些连表查询出来的对象怎么办?它算哪一层呢?它也是数据库里查询出来的,但是却没有一个确定的表与它一一对应。
而且创建对象的时候还要额外加点东西,比如你用的是MyBatisPlus,就需要在DO中那个非表结构的字段上加上一个@TableField(exist = false)这样的注解。不知道我是不是有强迫症,加上这些非表字段的属性,我就觉得这个实体类不纯洁了,被污染了已经。它已经不属于DO了,那它属于什么呢?
还有个问题就是关于VO的,它是由后端输出到前端的对象,但是如果我需要把DO直接输出到前端的时候怎么办,我需要创建一个跟DO属性一模一样的VO出来吗?按照规范,我是不是不能直接把DO输出到前端呢?还有经过Service层处理过的DTO与BO,难道也要创建一个属性一模一样的VO后再输出到前端?
接收上层查询请求的参数用Query,但是要是接收创建,修改,删除的参数用什么对象?DTO吗?有时候没有规范的情况下甚至用DO来接收参数,DO充斥着各种参数校验的注解,像@NotBlank, @NotEmpty之类的。这就更不纯洁了。
解决办法
这是我们公司开始新项目的时候讨论的问题,经过我们长达很多天的讨论与实践,最终决定:不过于追求每一层都用自己的领域模型,使用的时候宽泛但是也有规范。
DAO层
比如关于持久层对象DO,它有与数据库一一对应的实体;也有连表查询后的实体,没有一个确定的表与它一一对应。关于这个问题我们是这样解决的,将持久层对象重新定义,分成两个,一个叫PO,一个叫DO。
-
PO(Persistent Object):持久化对象,与数据库表结构形成一一对应的映射关系,没有额外的字段与注解。
-
DO(Data Object):此对象是数据库的查询结果,通过 DAO 层向上传输的数据源对象,比如连表查询的对象。
这样一来关于DAO层的领域对象就清晰了很多,分包管理也容易。那么还有一个问题就是关于DAO接收上层参数的问题。如果DAO要接收一个对象作为参数,这个对象叫什么O呢?DO? Query? DTO? BO?
仔细想想,DAO如果是新增的话,直接传一个PO对象即可。如果是修改,删除,查询之类的情况,需要接收的参数就会很少,大部分情况下都是一两个,可以直接写参数,而不用封装成对象。
如果真的需要一个对象,要传很多参数,也可以将Query,BO或DTO直接传入DAO层。可能会有人说,这样领域对象不就串层了吗?还是那句话,这种情况下就不必追求每一层都用自己的领域对象了。
其他层的参数传递
查询用Query这个应该是没有任何争议,非常的合适。但是创建,修改之类的参数用什么对象接收呢?我们用DTO
- DTO(Data Transfer Object):数据传输对象。
用DTO来接收前端传递的参数,再配合参数校验的注解,简直完美。Controller层直接用DTO接收参数,参数校验注解处理一遍,传递到Service层,业务逻辑处理一遍,到了DAO层要么转成一个PO对象保存,要么传递一两个参数进行修改,要么直接整个传递给DAO。
是不是只能给前端输出VO?
当然不是,对于一些简单的查询,我就是需要把DO或者PO传给前端怎么办?如果需要,那就直接传就可以,没必要纠结。
关于复杂的对象,需要组装的对象就可以组装成VO再返回给前端。关于这一点,我与领导有那么一点小小的分歧,领导觉得要规定只能在Controller层组装VO,我的意思是也可以在Service层组装成VO,这样Controller层只需要负责分发数据就可以,其他任何逻辑都不需要。但是领导觉得不合适,只能在Controller层组装VO。
那么这样一来,就会出现另一个问题,Service层如果需要一些复杂业务处理,需要调用多个Service放在一个事务里,最后处理完组成的这个对象叫什么?这个对象已经不需要Controller组装成VO了,可以直接返回给前端。但是VO又不能出现在Service中。
这时候我们就引入了另一个领域对象:BO
- BO(Business Object):业务对象,可以由 Service 层输出的对象。
既然不能叫VO,那就叫BO好了,这样一来,所有的领域对象就都很清晰了,不会再乱了。
总结
-
PO(Persistent Object):持久化对象,与数据库表结构形成一一对应的映射关系,没有额外的字段与注解。
-
DO(Data Object):此对象是数据库连表查询的结果,通过 DAO 层向上传输的数据源对象。
-
DTO(Data Transfer Object):数据传输对象,用于接收上层新增或修改等的参数,比如Controller层接收参数并通过注解的形式进行参数校验。
-
BO(Business Object):业务对象,由 Service 层输出的复杂对象,也可直接传递给前端
-
Query:数据查询对象,各层接收上层的查询请求。注意超过 2 个参数的查询封装,禁止使用 Map 类 来传输。
-
VO(View Object):显示层对象,通常是 Web 向前端传输的对象。
关于领域对象的思考就是这些了,虽然目前是这么定义的,但是能不能行还是得经过实践来检验。
不过我总觉得VO与BO重复了,如果宽泛一点,Service层为什么不可以返回VO呢?
刚开始的时候我会纠结,有些领域对象只有一两个字段不一样,有必要分开吗?
我觉得有必要。代码大部分时间是用来读的,可读性要比性能还重要。因为可读性高就意味着它的可维护性也高。

博客探讨了在实际项目中如何应用《阿里巴巴Java开发手册》中的领域模型规约,指出过于严格的分层可能导致对象多次转换,增加了不必要的复杂性。作者提出了一种简化方案:将DO分为PO(持久化对象)和DO(数据对象),分别对应数据库表结构和查询结果;DTO用于接收创建和修改参数,提高参数校验的便利性;BO(业务对象)用于Service层输出复杂对象;Query用于查询请求。此外,对于简单场景,允许直接使用DO或PO传递数据,而复杂的对象组装成VO。最后,总结了各个领域对象的角色和应用场景,强调了代码可读性和可维护性的优先级。
9690

被折叠的 条评论
为什么被折叠?



