领域驱动设计
什么是领域驱动设计?
领域驱动设计是一种由域模型来驱动着系统设计的思想,不是通过存储数据词典(DB表字段、ES Mapper字段等等)来驱动系统设计。领域模型是对业务模型的抽象,DDD是把业务模型翻译成系统架构设计的一种方式。
DDD与其他架构有什么区别?
案例引入
//Version 1.0
public class RegistrationServiceImpl {
private UserRepository userrepo;
private SalesRepRepository salseRepRepo;
public User register (String name,String phone) throws ValidationException {
//参数校验
if (name == null || name.length() == 0) {
throw new ValidationException("name");
}
if (phone == null || phone.length() == 0) {
throw new ValidationException("phone");
}
//获取 归属地areaCode 和 运营商编号operationCode 然后找到SalseRep
String areaCode = getAreaCode(phone);
String operationCode = getOperationCode(phone);
SelesRep rep = salseRepRepo.findRep(areaCode,operationCode);
//进行用户注册 创建用户并返回
User user = new User();
user.name = name;
user.phone = phone;
user.repId = rep;
return userRepo.save(user);
}
private String getAreaCode(String phone) {
//...
}
private String getOperationCode(String phone) {
//...
}
}
- 可拓展性差
- 若后期对注册方法进行拓展,添加其他多种注册方式
- 每种方法内都需要进行多次校验,存在大量重复代码。
- 若某一种类型的参数需要修改
- 需要针对多种方法进行修改,明显不符合开闭原则
- 若将校验函数统一封装在一个工具类内
- 检验过程仍在注册方法类内,检验过程中的异常和注册的业务逻辑异常耦合在一起
- 未来参数越来越多,校验逻辑不断膨胀导致后续维护性从差
- 若后期对注册方法进行拓展,添加其他多种注册方式
# 修改目标
1.增强可拓展性,最好带有自检性
2.参数校验逻辑可复用
3.参数校验异常和业务逻辑异常解耦
//Version 2.0
public class phoneNumber {
private final String number;
private final String pattern = "xxx";
public String getNumber() {
return number;
}
public phoneNumber(String number) {
if (number == null) {
throw new ValidationException("number");
} else if (isValid(number)) {
throw new ValidationException("格式错误");
}
this.number = number;
}
private boolean isValid(String number) {
return number.matches(pattern);
}
}
public class RegistrationServiceImpl {
private UserRepository userrepo;
private SalesRepRepository salseRepRepo;
public User register (String name,phoneNumber phone) throws ValidationException {
//获取 归属地areaCode 和 运营商编号operationCode 然后找到SalseRep
String areaCode = getAreaCode(phone);
String operationCode = getOperationCode(phone);
SelesRep rep = salseRepRepo.findRep(areaCode,operationCode);
//进行用户注册 创建用户并返回
User user = new User();
user.name = name;
user.phone = phone;
user.repId = rep;
return userRepo.save(user);
}
private String getAreaCode(String phone) {
//...
}
private String getOperationCode(String phone) {
//...
}
}
思考:
获取归属地和运营商编号是否属于注册业务内?
不属于。
为什么要在注册方法内获取归属地和运营商编号
为了对适配接口,对参数进行处理拼接
//Version 3.0
public class RegistrationServiceImpl {
private UserRepository userrepo;
private SalesRepRepository salseRepRepo;
public User register (String name,phoneNumber phone) throws ValidationException {
//获取 归属地areaCode 和 运营商编号operationCode 然后找到SalseRep
SelesRep rep = salseRepRepo.findRep(phone.getAreaCodeareaCode(),
phone.getOperationCodeoperationCode());
//进行用户注册 创建用户并返回
User user = new User();
user.name = name;
user.phone = phone;
user.repId = rep;
return userRepo.save(user);
}
}
DDD与其他架构有什么区别?
在传统mvc架构中M 表示 Model,V 表示 View,C 表示 Controller。它将整个项目分为三层:数据层、展示层、逻辑层。这就导致很多的数据层的类只包含数据,但是不包含业务逻辑。属于贫血模型
而作为DDD中的phoneNumber类,既包含了数据,又包含了数据校验,还有其相关属性的职责。属于充血模型。
贫血模型
- 贫血模型是一种领域模型,其中领域对象包含很少或没有业务逻辑。是一种面向过程的编程模式,将数据和过程结合在一起。
- 因为贫血模型没有逻辑实现,所以逻辑基本上会放到调用贫血模型的service中,这些service类会转换领域对象的状态。
贫血模型的优点
- 贫血模型的系统层次结构清楚,各层之间单向依赖
- 领域对象几乎只作传输介质之用,不会影响到层次的划分
贫血模型的缺点
- 对象状态和行为分离(贫血模型中,对象只有属性,get/set方法,业务逻辑在不在对象类内部),所以一个完整的业务逻辑描述不能在一个类中完成,而是一组相互协作的类共同完成的。
- 不够面向对象,领域对象只是作为保存状态或者传递状态使用。只有数据没有行为的对象不是真正的对象。
- 可复性很差,业务逻辑的描述能力较差。一个稍微复杂的业务逻辑,就需要太多类和太多代码去表达。
充血模型
- 数据和对应的业务逻辑被封装到同一个类中。因此,这种充血模型满足面向对象的封装特性,是典型的面向对象编程风格。
- 业务逻辑集中在 Service 类中。基于充血模型,Service 层包含 Service 类和 Domain 类两部分。Domain 是基于充血模型开发的,既包含数据,也包含业务逻辑。而 Service 类变得非常单薄。
- 充血模型中,绝大多业务逻辑都应该被放在domain里面,包括持久化逻辑,而Service层是很薄的一层,仅仅封装事务和少量逻辑。
优点
- 面向对象,符合单一职责,不像在贫血模型里面那样包含所有的业务逻辑太过沉重
- 适合于复杂的企业业务逻辑实现,可复用程度高。
缺点
- 如何划分业务逻辑,什么样的逻辑应该放在Domain中,什么样的业务逻辑应该放在Service 中,这是很含糊的。
- 对象自治度高的结果就是不利于大规模团队分工协作。
领域驱动架构
领域驱动设计没有特定的架构风格,它的核心是域模型驱动业务的思想,常见的领域驱动设计架构有经典的三层架构、REST架构、事件驱动架构、CQRS架构、六边形架构等。
领域和子域
在研究和解决业务问题时,DDD 会按照一定的规则将业务领域进行细分,当领域细分到一定的程度后,DDD 会将问题范围限定在特定的边界内,在这个边界内建立领域模型,进而用代码实现该领域模型,解决相应的业务问题。简言之,DDD 的领域就是这个边界内要解决的业务问题域。
领域可以进一步划分为子领域。我们把划分出来的多个子领域称为子域,每个子域对应一个更小的问题域或更小的业务范围。
领域的核心思想就是将问题域逐级细分,来降低业务理解和系统实现的复杂度。通过领域细分,逐步缩小服务需要解决的问题域,构建合适的领域模型。
子域划分
- 核心域:决定产品和公司核心竞争力的子域,它是业务成功的主要因素和公司的核心竞争力。
- 通用域:没有太多个性化的诉求,同时被多个子域使用的通用功能的子域。
- 支撑域:但既不包含决定产品和公司核心竞争力的功能,也不包含通用功能的子域。
DDD中的模型
Model与传统的POJO类等对比,都是一个类中有属性、属性有Get/Set方法,并且做传输对象。
Model与传统MVC三层架构层的业务逻辑层中的Service对比,都是处理业务行为(Action)层。
模型(Model)承载着业务的属性和具体的行为,是业务表达的方式、是DDD的内核。是一个类中有属性、属性有Get/Set方法,并且业务的行为(Action)操作也是在模型类中(充血模型)即做业务逻辑处理,又做数据传输对象,模型分为Entity、Value Object、Service这三种类型。
Entity (实体)
- 有特定的标识,标识着这个Model在系统中全局唯一
- 内部值可以是变化的,可能存在生命周期 (比如订单对象,状态值是连续变化的)
- 有状态的Value Object
Value Object (值对象)
- 内部值是不变的,不存在生命周期 (比如地址对象不存在生命周期)
- 无状态对象
Service (服务)
- 无状态对象
- 当一个属性或行为放在Entity、Value Object中模棱两可或不合适的时候就需要以Service的形式来呈现
领域驱动三层架构
API层
API层是作为对外打包、前端接口调用使用。
Domain层
Domian层是整个域模型,不能直接把它打包成maven给别人使用,也不能直接把它作为接口给前端使用,有些需要API层作为进行转换后调用Domain,对调用Domain返回的数据进行包装筛选后再返回出去。系统的核心层,所有具体的业务逻辑处理、事件处理等都在这层域模型中处理
Repository 层
数据源代理层,Repository 层类似一个网关代理,它本身没有数据,数据都是通过它的代理来被Domain层访问,被代理的数据源不仅仅可以是DB、ES还可以是HTTP、RPC任何与Domain层进行数据交互的都叫Repository
DP
在DDD里,DP可以说是一切模型、方法、架构的基础。它是在特定领域内、拥有精准定义、可以自我验证、拥有行为的对象。可以认为是领域的最小组成部分。
DP三条原则
- 让隐性的概念显性化
- 让隐性的上下文显性化
- 封装多对象行为
评论区
欢迎你留下宝贵的意见,昵称输入QQ号会显示QQ头像哦~