DDD 对于团队的上手成本太高了,需要每个人都理解 DDD 的思想,还要有个大佬开路,做决策,否则玩不起来,至少目前我司是弄不起来了,但是学习下其中的一些思想,对平常开发编写代码是大有裨益。
—— 2022-02-21
DDD 核心
- 辩证的看待每一个术语,灵活变动
- 事件驱动
- 既要耦合,又要解耦
- DDD 只是一套方法论,目的是写出好维护,易扩展的代码,借鉴它的思想和一些方法,并不是一昧的提倡用DDD,还是要根据业务和团队水平进行实施
DDD 的难点
建立领域模型
DDD 后续的操作都基于领域模型,所以一开始拿到需求后,应该是展开事件风暴,分析建立领域模型。
例如用户基本业务属性有:用户名,真实姓名,手机号,密码,所属省,所属市,所属区,所属单位,关联角色。传统的 CRUD 做法我们会创建一个用户表来维护这些属性,但是在 DDD 中,我们会对这里分析出事件,分别是:用户被新增事件、用户名被修改事件、用户被删除事件。从事件可以推出,用户是我们的领域模型,那这个用户领域模型,应该包含哪些字段呢?
用户存在这些属性:id,用户名,真实姓名,手机号,密码,所属省,所属市,所属区,所属单位,关联角色。
对于用户领域模型来说,省、市、区地址相关属性是值对象,对用户领域模型来说不存在独立的生命周期。而角色是有独立的生命周期,能独立处理业务逻辑,是一个独立的限界上下文。
通过业务逻辑,来定义领域模型,将领域能力定义在领域模型内部。
定义用户聚合根:
- AggregateRoot 聚合根标识
- 聚合根中的属性不是和数据库字段一一对应的,而是更偏向于业务语义
聚合根中没有 set 方法,因为每一个属性的变更是一个业务逻辑,传统的做法是放在业务上去处理,DDD 中属性的变更应该是在模型内的,模型是具有感知能力的
/**
* 用户聚合根
*
* @author baiyan
*/
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Slf4j
public class User implements AggregateRoot {
/**
* 用户id
*/
private Long id;
/**
* 用户名
*/
private String userName;
/**
* 用户真实名称
*/
private String realName;
/**
* 用户手机号
*/
private String phone;
/**
* 用户密码
*/
private String password;
/**
* 用户地址
*/
private Address address;
/**
* 用户单位实体
*/
private Unit unit;
/**
* 角色实体
*/
private List<Role> roles;
/**
* 创建时间
*/
private LocalDateTime gmtCreate;
/**
* 修改时间
*/
private LocalDateTime gmtModified;
//省略部分逻辑
/**
* 修改用户名
*
* @param userName 修改后的用户名
* @param existUser 根据修改后的用户名查询出来用户
*/
public void bindUserName(String userName,User existUser){
ValidationUtil.isTrue(Objects.isNull(existUser) || Objects.equals(existUser.getId(),this.id),"user.user.name.is.exist");
this.userName = userName;
}
}
定义地址值对象:ValueObject 是值对象标识 ```java /**
- 地址值对象 *
@author baiyan */ @Getter @AllArgsConstructor @NoArgsConstructor public class Address implements ValueObject
{/**
- 省 */ private String province;
/**
- 市 */ private String city;
/**
- 区 */ private String county;
/**
- 比较地址相等 *
- @param address 地址
- @return */ @Override public boolean sameValueAs(Address address){ return Objects.equals(this,address); }
}
<a name="Fcp1N"></a>
## 仓储层
仓储层是将领域模型映射成数据模型,只是简单的模型映射转换,但是要注意的是,DDD 中对同一数据不允许有多个修改入口,这样会导致数据一致性难以维护或者说业务逻辑重复。所以针对这里角色和用户是多对多的关系,传统的做法会设计一个表来维护角色和用户的关系,但是这样会有两个数据模型,和前面提到的有冲突了,因此不能单独设计一个表来维护,而是把这些关系放在用户表中来维护。
用户表:
```java
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("t_user")
public class UserPO extends BaseUuidEntity {
/**
* 用户名
* */
private String userName;
/**
* 真实姓名
* */
private String realName;
/**
* 手机号
* */
private String phone;
/**
* 密码
* */
private String password;
/**
* 关联的单位id
*
* */
private Long unitId;
/**
* 关联的角色id列表
* */
private String roleIds;
/**
* 省
*/
private String province;
/**
* 市
*/
private String city;
/**
* 区
*/
private String county;
}
DDD 术语
领域
领域是一个业务方向,例如电商中,订单、物流、商品这些都是划分为一个领域。
子域
子域是对领域的二次划分。例如领域订单的子域可以划分为子订单、母订单。
核心域
支撑域
通用域
它的核心体现在高兼容性,在订单中可以用,挪到物流中也可以用。
限界上下文
用来定义领域业务的边界
防腐层
上下文A和上下文B不能直接调用而是要通过防腐层来处理,非常重要