DDD 学习笔记 | 极客时间 | 手把手教你落地 DDD
🏆 原文:16|聚合的实现(中):怎样实现不变规则?
业务规则必须在领域层实现。
当聚合根已经拥有了实现业务规则所需要的数据,首选直接在聚合根里实现业务,而不是在应用服务里实现。
如果业务较为复杂,可以考虑将业务规则封装到领域服务里。
聚合根中的业务规则,一般需要在聚合根内部完成。例如:某一个单独的技能(Skill)或工作经验(WorkExperience)对象自身是无法校验的,必须从员工聚合整体上考虑。
Assembler(装配器)
可以转换领域对象与应用层的 DTO
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
| package chapter15.unjuanable.application.orgmng.empservice;
// imports...
@Component
public class EmpAssembler {
EmpHandler handler; // Emp的领域服务
OrgValidator orgValidator;
@Autowired
public EmpAssembler(EmpHandler handler, OrgValidator orgValidator) {
this.handler = handler;
this.orgValidator = orgValidator;
}
// 由 DTO 生成领域对象
Emp fromCreateRequest(CreateEmpRequest request, User user) {
//校验参数
validateCreateRequest(request);
// 生成员工号
String empNum = handler.generateNum();
Emp result = new Emp(request.getTenantId(), user.getId());
result.setNum(empNum)
.setIdNum(request.getIdNum())
.setDob(request.getDob())
.setOrgId(request.getOrgId())
.setGender(Gender.ofCode(request.getGenderCode()));
request.getSkills().forEach(s -> result.addSkill(
s.getSkillTypeId()
, SkillLevel.ofCode(s.getLevelCode())
, s.getDuration()
, user.getId()));
request.getExperiences().forEach(e -> result.addExperience(
e.getStartDate()
, e.getEndDate()
, e.getCompany()
, user.getId()));
return result;
}
void validateCreateRequest(CreateEmpRequest request) {
//业务规则:组织应该有效
orgValidator.orgShouldValid(
request.getTenantId(), request.getOrgId());
}
// 将领域对象转换成 DTO
EmpResponse toResponse(Emp emp) {
// ...
}
}
|
Builder 与 Assembler 比较
- Builder 是工厂模式的一种实现,工厂位于领域层,入口参数可以是基本类型、领域对象或者在领域层定义的 DTO,但不能是在应用层定义的 DTO
- 优点:对领域逻辑的封装更彻底一些
- 缺点:要写更多的代码和数据转换逻辑
- Assembler 位于应用层,入口参数可以是应用层定义的 DTO
Assembler 的命名只是一种常见的习惯,目的是和领域层的工厂相区别。
像 assembler 这样对 service 起辅助作用的类,一般统称为 Helper。
Repository(仓库) 和传统的 DAO(数据访问对象)的区别
虽然都用来访问数据库,但有一个重要的区别:DAO 是针对单个表的,而 Repository 是针对整个聚合的。