DDD 值对象 DDD 领域驱动设计 实体 值对象 聚合与聚合根 仓储 Repository 防腐层

Go语言DDD实战初级篇-值对象 https://mp.weixin.qq.com/s/nZQh4pbkC_hut8FuQECTXQ

【公告】淘宝 npm 域名即将切换 && npmmirror 镜像源大重构升级 https://www.yuque.com/egg/cnpm/refactor-2022

同时,目录结构基于 DDD 领域驱动设计方式,https://www.yuque.com/liberty/rf322x

app
├── common
│   └── adapter     # 外部服务调用
├── core
│   ├── entity      # 核心模型,实现业务行为
│   ├── event       # 异步事件定义,以及消费,串联业务
│   ├── service     # 核心业务逻辑
│   └── util    
├── repository
│   └── model      # ORM 模型,数据定义
├── port
│   └── controller # HTTP Controller
├── schedule       # 定时任务
└── test           # 单测

 

 

 

引言 https://www.yuque.com/liberty/rf322x/ny150b

https://www.yuque.com/egg/ddd/vgvvu0

 

实体与值对象

实体实体一定是具有唯一的标记的,这个标记应该全局唯一,一般可以采用 uuid、bson id 等,也可以自己基于数据库生成实体是 DDD 的基础对象,实体有三个基础特性:ID 的唯一性:实体一定是具有唯一的标记的,这个标记应该全局唯一,即使其他的属性或行为完全一样,只要 ID 不一样就不是一个实体。 例如: 每个人都有身份证号,两个人即使身高、体重、甚至名字完全一样,只要他们的身份证号不一样他们就不是一个人,在这种场景下 人 这个对象就是由身份证号作为唯一标记的实体生命周期能力:实体是具有生命周期的,实体创建完后会被持久化,然后在某个时间重建出来再执行具体的业务,然后再被持久化,这也是 Repository 存在的核心互不依赖性:实体之间不能互相持有引用,只能持有其他实体的唯一 ID。比如在我们的 demo 中,AuthPolicy 持有了 Account 的唯一标记 accountId,但是它不能持有 Account 的引用
 
Java
 
 
 
// 持有引用,不允许
public class AuthPolicy {
 
private Account account;
}
 
// 持有唯一 ID 标记,允许
public class AuthPolicy {
 
private String accountId;
}
 
 
值对象领域对象除了实体以外还包含值对象,值对象和实体最本质的区别就是没有唯一的 ID,两个属性只要一样,那么就认为这两个对象一样,比如一个 Address 对象,只要属性相同它就代表同一个意思,这时候它就是一个值对象。值对象也有几个基础特性:
 
Java
 
 
运行代码
 
复制代码
 
 
 
 
public static class Rule {
@Getter
private String effect;
@Getter
private String[] action;
@Getter
private String resource;
 
public Rule(String effect, String[] action, String resource) {
this.effect = effect;
this.action = action;
this.resource = resource;
}
 
public boolean equal(Rule rule) {
return resource.equals(rule.getResource())
&& effect.equals(rule.getEffect())
&& Arrays.equals(action, rule.getAction());
}
}
 
 
值对象不变性:值对象是不可变的,值对象创建完后不允许再修改,所以在值对象的编码中,我们不提供外界可访问的 Setter 方法在 DDD 中实体/聚合之间是不能保持对方的引用的,也就是说实体不能持有另一个实体的引用,只能持有另一个实体的ID。但是值对象只是一个数据的载体,所以在 DDD中 值对象是可以在多个实体间共享的,这时候如果值对象允许更改那么可能会造成 实体 A 修改了某个值对象意外影响到了 实体 B 的业务的情况,所以值对象应该设计为不可变的。方法无副作用:值对象的方法都应是无副作用方法,即在方法的执行前后对象的状态是不会改变的,因为如果方法不是无副作用的,那执行过程中状态会发生更改,也就破坏了值对象的不变性值对象的不变性很关键,在模型设计的时候一定要做好限制,如果需要修改值对象的状态,那就重新新建一个,保证创建完后的不变性。。对比
类型
是否需要唯一标记
是否可变
是否能持有实体引用(不同聚合根)
是否可替换
实体
值对象
领域对象 = 实体 + 值对象 ,在 DDD 的建模中,我们应该根据不同的业务需求选择建实体还是建值对象,对于这两个之间如何选择,有一个经验:如何一个模型是需要有生命周期和唯一性 (有持久化和后续重建修改) 的需求那么应该建模成实体,否则应该建模成值对象

 

 

要理解什么是聚合我们先看一个案例,在授权系统中,我们有很多的实体和值对象,这些实体和值对象之间是一种松散的关系

image

 

聚合的特性

 

  • 强一致性:聚合是一组业务逻辑的统一约束单元,通过聚合可以将相关的业务逻辑组合到一起形成强约束,避免出现业务一致性的问题。同时通过聚合能让业务关系更清晰,比如 Group1 归属用户账号域,Group2 归属调用账号域,也更便于后续的系统设计

 

  • 聚合间不能互相引用:聚合之间不能通过对象引用的方式直接依赖,只能通过互相持有对方的唯一 ID 来进行关联。因为如果互相之间持有引用,那就会出现聚合内状态被其他聚合修改的情况,破坏了聚合本身的高内聚状态

 

  • 必须通过聚合根访问:一个聚合只能有一个访问入口,这个入口称为聚合根。聚合的所有操作都必须经过聚合根,不能绕过聚合根直接操作内部实体,如上述用户账号的聚合,UserAccount + AuthPolicy 组成一个聚合,UserAccount 作为聚合根所有的操作都需要通过它来进行,外部不能直接对 AuthPolicy 进行任何操作

聚合与聚合根

image

 

image

防腐层在 DDD 中是一个适配组件,主要用于防止系统代码被外部代码所影响,避免因为外围依赖的变化而导致系统代码的腐化,大致的结构如下

 

 

防腐层的作用很明确,将外部服务进行解耦,屏蔽外部服务的变化,虽然会增加很多转换的代码,但是从长远来看收益是远高于冗余代码的成本的,换个角度看,repository 就是针对持久化的一个防腐层。加入防腐层后我们再来看上面的样例

 

 

 

image

 

 

 

 

 

 

posted @ 2022-12-22 21:10  papering  阅读(84)  评论(0)    收藏  举报