分层领域模型简介 其他网址
04.分层领域模型使用解读 - 个人文章 - SegmentFault 思否摘自:《阿里巴巴Java开发手册》
一、分层领域模型规约
二、领域模型命名规约
项 说明 DO(Data Object) 与数据库表结构一一对应,通过DAO层向上传输数据源对象。 DTO(Data Transfer Object) 数据传输对象,Service或Manager向外传输的对象。若作为分布式服务的参数或返回对象,通常要实现序列化接口。 BO(Business Object) 业务对象。由Service层输出的封装业务逻辑的对象。
此对象在实际使用中有不同的理解,有的团队采用领域驱动设计,BO 含有属性和方法(具体可参考领域驱动设计的相关图书);有的团队将 BO 当做 Service 返回给上层的 “专用 DTO” 使用;而有的团队则当做 Service 层内保存中间信息数据的 “DTO” 或者上下文对象来使用(本文采用这种理解)。
比如 BO 中可以保存中间状态,放一些逻辑等,这些并不适合放在 DTO 中
AO(Application Object) 应用对象。在Web层与Service层之间抽象的复用对象模型,很贴近展示层,复用度低。
有些团队会将前端查询的属性和保存的属性几乎一致的对象封装为 AO,如读取用户属性传给前端,用户在前端编辑了用户属性后传回后端。这种用法将 AO 用作 Param 和 VO 或 Param 和 DTO 的组合。
VO(View Object) 显示层对象,通常是Web向模板渲染引擎层传输的对象。
通常控制层将其作为JSON 返回给前端然后前端渲染,或者加载页面模板在后端进行填充。
Query 数据查询对象,各层接收上层的查询请求。注意超过2个参数的查询封装,禁止使用Map类来传输。
Param 为查询参数对象,适用于各层,通常用作接受前端参数对象。Param 和 Query 的出现是为了避免使用 Map 作为接收参数的对象。
1) 数据对象:xxxDo,xxx即为数据表名。为什么要有分层领域模型? 有的朋友查询参数喜欢通过 Map 或者 JSONObject 来封装。有些朋友可能会认为这么多模型没有必要,因为通常各层模型的属性基本相同,而且各种类型的分层模型对象转换非常麻烦。
2) 数据传输对象:xxxDto xxxBo,xxx为业务领域相关的名称。
3) 展示对象:xxxVo,xxx一般为网页名称。
4) POJO是DO/DTO/BO/VO的统称,禁止命名成xxxPOJO。
使用不同的分层领域模型能够让程序更加健壮、更容易拓展,可以降低系统各层的耦合度。
分层模型的优势只有在系统较大时才体现得更加明显。设想一下如果我们不想定义 DTO 和 VO,直接将 DO 用到数据访问层、服务层、控制层和外部访问接口上。此时该表删除或则修改一个字段,DO 必须同步修改,这种修改将会影响到各层,这并不符合高内聚低耦合的原则。通过定义不同的 DTO 可以控制对不同系统暴露不同的属性,通过属性映射还可以实现具体的字段名称的隐藏。不同业务使用不同的模型,当一个业务发生变更需要修改字段时,不需要考虑对其它业务的影响,如果使用同一个对象则可能因为 “不敢乱改” 而产生很多不优雅的兼容性行为。
如果我们不愿意定义 Param 对象,使用 Map 来接收前端的参数,获取时如果采用 JSON 反序列化,则可能出现上一节所讲到的反序列化类型丢失问题。如果我们不使用 Query 对象而是 Map 对象来封装 DAO 的参数,设置和获取的 key 很可能因为粗心导致设置和获取时的 key 不一致而出现 BUG。
流程图 查询视图
返回视图
文章图片
前端或者其它服务将 Param 对象作为参数传给控制层或者对外服务接口,然后调用内部的服务类,服务类内部的中间数据和这些数据相关的逻辑可以封装为 BO ,比如根据 BO 多个属性判断是否符合某个条件。
如果查询数据则封装为 Query 对象作为参数,如果需要查询其它依赖,则可以封装 Param 对象作为参数去查询。DAO 层一般插入和更新的参数对象使用 DO 或 Param, 查询参数一般使用 Query,删除参数一般使用 Param。
总结 也有部分团队 RPC 的请求和响应参数都通过 DTO 来承载,通过 XXRequestDTO 和 XXResponseDTO 来表示。
文章图片
数据访问层(DAO)通常将数据封装为 DO 对象传给 Service 层,Manager 或 Client 层往往将查询结果封装为 DTO 传给 Service 层。
通常内部服务层通过 DTO 往外传输数据。Controller 通常将 DTO 组装为前端需要的 VO 或者直接将 DTO 外传 。
RPC 服务接口将 DTO 直接返回或者重新封装为新的 DTO 返回给外部服务。
另外即使同一个接口,但是一个对内使用,一个对外暴露,尽量使用不同接口,定义不同的参数和返回值,从而避免因为修改内部或外部的数据结构而导致另外一个受到影响,这也是单一职责原则的要求。单一职责原则:一个类应该有且只有一个改变的理由。
实践分层领域模型能够提高项目的健壮性、可拓展性和可维护性,降低了系统内部各层的耦合度。
上面只是给出一种参考,很多团队对部分分层模型的理解会有差异,实际的使用过程中根据自己团队的规模可以适当变通。比如有很多团队项目并不是特别大,为了降低复杂度,只用到了 DTO 、VO 、DO 三种分层领域模型。
提倡在 DTO 中写逻辑,不要在 RPC 返回对象的 DTO 中封装逻辑。
有些团队的个别成员会将根据成员属性作判断的一些函数写到 DTO 中,最奇葩的是该逻辑还主要供内部系统业务层使用。如:
public class xxDTO{
// 各种属性// 逻辑代码
public static boolean canXXX(){
// 各种判断
}
}
【项目软知识系列--分层领域模型】这样造成系统的耦合性非常强。
如果对方用到了这个函数,未来此函数的内部逻辑必须发生变化,未必能及时通知对方升级,容易造成 BUG。
即使耗费了成本找到了使用方,为了你的功能,让别人被迫升级版本重新上线也是非常不专业的事情。
显然这样做不合理。
试想一下今天 A 部门告诉你他们因某个功能被迫修改了某个 RPC 返回值 DTO 的某个方法,你们用到没有?用到升级一下哈…
然后 B 部门的人明天告诉你同样的话,然后 C 部门,然后…
你会不会崩溃?
建议如果需要在内部业务中写对实体相关的逻辑,可以考虑封装到工具类 / 帮助类中。