文章摘要(AI生成)
业务场景在日常的开发过程中,我们总会不经意间写出面向数据库编程的代码,对一个简单的用户登录而言,我们常用的逻辑如下:public UserDto login(String username, String password){ UserDo userDo = userMapper.select
业务场景
在日常的开发过程中,我们总会不经意间写出面向数据库编程的代码,对一个简单的用户登录而言,我们常用的逻辑如下:
public UserDto login(String username, String password){
UserDo userDo = userMapper.selectByName(userName);
if(user == null){
System.out.println("用户不存在~");
}else{
if(!user.getPassword().equals(password)){
System.out.println("密码错误~");
}
}
UserDto userDto = new UserDto();
UserDetail userDetail = userDetailMapper.selectByUserId(user.getId());
userDto.setUserDetail(userDetail);
UserPhone userPhone = PhoneDubbo.getByUserName(user.getName());
userDto.setUserPhone(userPhone)
return userDto;
}
在这个设计思路中,会有一下几个隐患:
- 当数据字段变更时,对应的校验逻辑可能会失效,login方法要进行改动
- 当外部依赖的dubbo接口入参变更时,login方法也要进行改动。
在上述的登录逻辑中,我们所进行的编码是面向数据库编程,即是面向具体实现的编程。在实现过程中,登录校验与用户信息查询,我们都在登录方法中进行了一一实现,这就导致了我们依赖的数据库、ORM框架、RPC服务发生变更时,都会影响到我们这个登录方法的编码,都需要我们对全流程进行测试回归。
在DDD中,我们引入了Entity、Repository、Domain Service来帮助我们将自己内部的业务逻辑聚合和外部服务隔离。将我们的编程转变为面向抽象接口的编程。
public UserInfo login(String username, String password){
UserInfo user = new UserInfo(username, password);
checkUserService.check(user);
fillUserService.buildUserInfo(user);
return user;
}
Entity-领域实体对象
实体是唯一的且可持续变化的。意思是说在实体的生命周期内,无论其如何变化,其仍旧是同一个实体。唯一性由唯一的身份标识来决定的。可变性也正反映了实体本身的状态和行为。
同时,我们的业务实现应该面向领域实体,而不是面向DO、DAO。针对登录逻辑,我们将用户抽象为领域实体,它包含我们所有用户相关的信息。我们的service都是对用户的状态进行改变。
为什么要使用实体?
当我们需要考虑一个对象的个性特征,或者需要区分不同的对象时,我们引入实体这个领域概念。
DDD的实体都做了些什么?
传统的实体只做值得传递作用,这无疑是相对浪费资源的,DDD的思想就是在实体中存在一些业务,例如:生成订单号,判断金额不能低于0.01等业务,这样可以减轻service层的压力。
DTO、VO、PO
DTO: 主要作为Application层的入参和出参,比如CQRS里的Command、Query、Event,以及Request、Response等都属于DTO的范畴。DTO的价值在于适配不同的业务场景的入参和出参,避免让业务对象变成一个万能大对象。
VO:值对象,通常用于业务层之间的数据传递。restful使用VO来针对视图显示,在web上传递。可以用一个VO对象对应整个界面的值。
DO:实际上是我们在日常工作中最常见的数据模型。但是在DDD的规范里,DO应该仅仅作为数据库物理表格的映射,不能参与到业务逻辑中。为了简单明了,DO的字段类型和名称应该和数据库物理表格的字段类型和名称一一对应,这样我们不需要去跑到数据库上去查一个字段的类型和名称。(当然,实际上也没必要一摸一样,只要你在Mapper那一层做到字段映射)
Repository-数据访问抽象
在传统的数据库驱动开发中,我们会对数据库操作做一个封装,一般叫做Data Access Object(DAO)。DAO的核心价值是封装了拼接SQL、维护数据库连接、事务等琐碎的底层逻辑,让业务开发可以专注于写代码。
数据库在本质上属于”硬件“,DAO 在本质上属于”固件“,而我们自己的代码希望是属于”软件“。但是,固件有个非常不好的特性,那就是会传播,也就是说当一个软件强依赖了固件时,由于固件的限制,会导致软件也变得难以变更,最终让软件变得跟固件一样难以变更。
使用Repository,本质就是对数据访问进行了封装。在业务层面上屏蔽了数据库变更对我们业务逻辑的影响。
Domain Service-领域服务
涉及多个领域实体对象状态改变的服务被称为Domain Service
Domain Service是我们对业务逻辑的抽象,一个业务过程的每一步该如何实现我们通过domain service来进行封装。
但是这个业务逻辑,是对多个领域实体对象状态的改变,如果是对单个对象的状态变更,我们只需要将这段业务逻辑放到相应的DP中即可。影响实体对象的外部操作可能是数据库、RPC、ORM等等的外部服务的变化,都可以通过Domain Service进行封装。
那么,业务入口、不依赖领域的功能实现都是写在哪里呢?
DDD中有三种service。分别是application service, domain service, infrastructure service。application service是应用程序的某个功能的入口,infrastructure service实现不依赖于业务(domain)的功能。
总结
归纳一下,我们遇到需求时,处理的思路可分为以下几步:
- 业务问题总览
- 划分领域对象,明确每个领域对象包含的信息和职责边界
- 在上层应用中根据业务描述编排entity和domain service
- 下水道工作,对下层的数据访问、RPC调用进行实现
评论区