写在前面
刚开始学Java EE开发的时候,无意中看到了阿里巴巴的《Java 开发手册》,于是就快速通读了一遍,但受限于当时的知识积累,很多都看不懂。但是领域模型这部分是比较有印象的,虽然平时在学习SSM和Spring Boot没用到过,但是在真正的项目开发中,是一定要用到的。
现在结合现在自己开发的项目来记录一下领域模型这部分内容。经过几个月的框架学习,最终还是感觉以任务驱动的方式学习会比较好,平时要多做应用到实际中,哪怕是小Demo。
探索
什么是实体?
首先,看下王珊老师在《数据库系统概论》中对实体的定义。
客观存在并可相互区别的事物称为实体。实体可以是具体的人、事、物,也可以是抽象的概念或联系。例如, 一个职工、一个学生、一个部门、一门课、学生的一次选课、部门的一次订货、教师与院系的工作关系( 即某位教师在某院系工作)等都是实体。
根据定义整理下来,实体有两类:
- 具体的人、事、物
- 抽象的概念或联系
概念好像挺抽象的,结合一下我之前做的数据库设计来理解一下吧。首先,来看一下数据库设计的步骤:
- 1. 需求分析:根据用户的需求,对在线购物网站进行需求分析,明确设计任务和要求,得出功能模块图以及数据流图和数据字典。
- 2. 概念结构设计:根据需求说明书,抽象出在线购物网站的实体以及得出各子系统的分E-R图,并且最后汇总成全局E-R图,形成不依赖于任何具体机器的数据模型,即概念模型。
- 3. 逻辑结构设计:将概念结构设计中的全局E-R图,按照E-R图到关系数据模型的转换规则转换为关系模式。根据要求,每一个关系模式需要达到3NF。
- 4. 物理结构设计:根据具体业务需求,设计好字段的类型,长度等,并使用数据库管理系统建立数据库以及相关的表。
- 5. 数据库实施与维护:用关系数据库管理系统提供的数据定义语言和其他实用程序将数据库逻辑设计和物理设计结果严格描述出来,成为关系数据库管理系统可以接受的源代码,再经过调试产生目标模式,然后组织数据入库 。
跳过第一步需求分析,来看一下第二步概念结构设计中的E-R图。
在这里的E-R图,实体有:
- 具体的人、事、物:用户,评论,管理员,订单,商品,购物车
- 抽象的概念或联系:购物车项,订单项
需要注意的是,不一定所有的实体间的联系都是实体,例如用户与订单的管理关系,用户和购物车的管理关系等。
根据数据库设计的步骤,E-R图所在的概念结构设计的后一步就是逻辑结构设计。把概念结构设计中的实体转换成关系模式,然后再进行规范化分析。如果满足特定的规范分析,不需要继续分解。那么这个关系模式就是最后的数据库表。
也就是说,实体 = 数据库表。
模型和领域模型有什么关系?
要明白什么是领域模型,首先需要知道模型是什么,下面是维基百科对MVC模式中的M模型的定义。
模型(Model)用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法。
根据定义整理下来,模型有两类:
- 封装数据的模型 - 功能
- 封装逻辑的模型 - 实体类
在一个三层架构的应用案例中,User类,UserDao类都可以称为Model。
- User类:User实体类,表示与数据库User表的映射结果。
- UserDao类:User持久层操作类,表示对User实体进行增删改查。
根据我的理解,领域模型其实就是模型中的第二类:封装逻辑的模型 - 实体类。
结合阿里规范理解领域模型
看下维基百科对领域模型的部分定义。
领域模型(或称域模型;英语:domain model)可以被看作是一个系统的概念模型,用于以可视化的形式描述系统中的各个实体及其之间的关系。
根据定义,也印证了上面所说的:领域模型就是封装逻辑的模型,即实体类。
为了更好的阐述领域模型,在下面我们来看一下阿里巴巴的《Java 开发手册》相关内容。
- 应用分层示意图:图中默认上层依赖于下层,箭头关系表示可直接依赖,如:开放接口层可以依赖于Web层,也可以直接依赖于 Service层,依此类推:
分层领域模型规约:
- DO(Data Object):此对象与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。
- DTO(Data Transfer Object):数据传输对象,Service 或Manager向外传输的对象。
- BO(Business Object):业务对象,由Service层输出的封装业务逻辑的对象。
- AO(Application Object):应用对象,在 Web 层与Service层之间抽象的复用对象模型,极为贴近展示层,复用度不高。
- VO(View Object):显示层对象,通常是 Web 向模板渲染引擎层传输的对象。
- Query:数据查询对象,各层接收上层的查询请求。注意超过2个参数的查询封装,禁止使用Map类来传输。
其实,不同的领域模型是基于分层结构下的产物。那么,为什么需要这么多的领域模型呢?其实就是为了适应不同层的需求。对于Web层来说,在现在前后端分离的环境下,一般都是提供接口的前端使用。而输出的对象肯定是不可以是上述的User实体类的。毕竟并不是User数据库表中的所有字段都可以给客户看的,例如密码等,因此在不同的层需要有不同的领域模型对应。
把领域模型应用到实际项目中
看到这么多定义,我看着也是很烦,而且没有必要为了遵循阿里的规范刻意去划分这么多的领域模型。还是那句话,适合就好了。
根据我自己的需求我采用的是DO,DTO,VO。下面为一个User实体的在不同层的不同领域模型案例。
- DO:其实就是实体,但是我一般把他叫做entity,例如:User
public class User {
private Integer userId;
private String username;
private String password;
private String phoneNumber;
private Long createTime;
private String avatarUrl;
private String team;
//省略getter和setter
}
- DTO:作为前端向后端请求参数的封装对象,例如:LoginDTO
public class LoginDTO {
private String phoneNumber;
private String password;
//省略getter和setter
}
- VO:作为后端向前端响应参数的封装对象,例如:UserVO
public class UserVO {
private Integer userId;
private String username;
private String phoneNumber;
private Long createTime;
private String avatarUrl;
private String team;
//省略getter和setter
}
对于对象转换,我们可以使用Spring或者Apache提供的BeanUtils.copyProperties
进行对象转换。需要注意的是,并不支持集合类型的转换,但是利用BeanUtils.copyProperties
自定义一个ListUtils来满足需求,代码如下所示:
public class ListUtil {
public static <S, T> void copyProperties(List<S> source, List<T> target, Class<T> tClass) {
try {
if (source.size() != 0){
for(S item : source){
T newItem = tClass.newInstance();
BeanUtils.copyProperties(item,newItem);
target.add(newItem);
}
}
}catch (Exception e){
e.printStackTrace();
}
}
}
还有需要注意的是,尽量把对象转换的工作都放在业务层中实现,减少控制器的代码量。