DDD领域驱动设计-聚合

将实体和值对象划分为聚合并围绕着聚合定义边界。选择一个实体作为每个聚合的根,并仅允许外部对象持有对聚合根的引用。作为一个整体来定义聚合的属性和不变量,并把其执行责任赋予聚合根或指定的框架机制。

为什么需要聚合

领域模型内的实体和值对象好比个体,而能让实体和值对象协同工作的组织就是聚合,用来确保这些领域对象在实现共同的业务逻辑时,能保证数据的一致性。

聚合由业务和逻辑紧密关联的实体和值对象组合而成,是数据修改和持久化的基本单元,每个聚合对应一个仓储,实现数据的持久化。

聚合有一个聚合根和上下文边界:

  • 该边界根据业务单一职责和高内聚原则,定义了聚合内部应该包含哪些实体和值对象

  • 聚合之间的边界是松耦合的

按这种方式设计出来的微服务自然就是高内聚、低耦合。

聚合属于领域层,领域层包含多个聚合,共同实现核心业务逻辑。聚合内的实体以充血模型实现个体业务能力,以及业务逻辑的高内聚。

聚合根

为避免由于复杂数据模型缺少统一的业务规则控制,而导致聚合、实体之间数据不一致。

传统数据模型中的每一个实体都是同级对等,若任由实体无管控地调用数据修改,可能导致实体之间数据逻辑的不一致。而若使用锁则会增加代码复杂度,降低系统性能。

若把聚合比作组织,则聚合根就是该组织负责人。聚合根也称为根实体,它不仅是实体,还是聚合的管理者。

  • 作为实体,拥有实体的属性和业务行为,实现自身的业务逻辑

  • 作为聚合的管理者,在聚合内部负责协调实体和值对象按照固定业务规则协同完成共同的业务逻辑

在聚合间,它还是聚合对外的接口人,以聚合根ID关联的方式接受外部任务和请求,在上下文内实现聚合之间的业务协同。即聚合间通过聚合根ID关联引用,若需要访问其它聚合的实体,就要先访问聚合根,再导航到聚合内部实体,外部对象不能直接访问聚合内实体

以订单为例,订单在聚合里是聚合根,与订单关联的有订单明细和收货地址:

  • 订单明细包括商品ID、商品名称、价格及数量等。由于订单明细是多个,它是一个集合,它被设计为实体,被订单引用

  • 订单只有一个收货地址,收货地址的值源于你的个人中心维护的收货地址,收货地址只能被整体替换,所以设计为值对象

聚合设计原则

原则一:通过唯一标识去引用其他聚合

  1. 引用聚合和被引用的聚合不可以在同一个事务中进行修改

  2. 如果你在试图在单个事务中修改多个聚合,这往往意味着此时的一致性边界是错误的,发生这样的情况通常是我们遗留了某些建模点,或者尚未发现通用语言中的某个概念。

  3. 当试图修改多个聚合的话,我们也应该采用最终一致性而非原子一致性。

public class Order {
    private Product product;
}

应改为利用唯一标识去引用其他聚合。

public class Order {
    private ProductId productId;
}

原则二:利用应用层来处理聚合内的依赖关系,避免在聚合中使用资源库或者领域服务。

如果实在需要特定的复杂依赖关系,可以在聚合的命令方法中使用领域服务和资源库。

原则三:在边界之外使用最终一致性。

如果单次用户请求,的确需要修改多个聚合实例的话,比如在一个聚合上执行命令方法时,如果还需要在其他的聚合上执行额外的业务规则,那么则需要使用最终一致性。我们可利用消息中间件之类的机制,完成最终一致性。

代码示例

/**
 * 用户
 *
 * @author haoxin
 * @date 2021-02-02
 **/
public class User implements Entity {

    /**
     * UserId
     */
    private UserId userId;

    /**
     * 用户名
     */
    private UserName userName;

    /**
     * 状态
     */
    private StatusEnum status;

    /**
     * 账号
     */
    private Account account;

    /**
     * 当前租户
     */
    private TenantId tenantId;

    /**
     * 角色Id列表
     */
    private List roleIds;


    public User(UserId userId, UserName userName, StatusEnum status, Account account, TenantId tenantId, List roleIds) {
        this.userId = userId;
        this.userName = userName;
        this.status = status;
        this.account = account;
        this.tenantId = tenantId;
        this.roleIds = roleIds;
    }

    public User(UserName userName, Account account, List roleIds) {
        this.userName = userName;
        this.account = account;
        this.roleIds = roleIds;
        this.status = StatusEnum.ENABLE;
    }

    /**
     * 是否有效
     * @return
     */
    public boolean isEnable() {
        return status == StatusEnum.ENABLE;
    }

    @Override
    public boolean sameIdentityAs(User other) {
        return other != null && userId.sameValueAs(other.userId);
    }

    /**
     * 禁用
     */
    public void disable() {
        StatusEnum status = this.status == StatusEnum.DISABLE?StatusEnum.ENABLE:StatusEnum.DISABLE;
        this.status = status;
    }

    public void refreshToken(String tokenStr) {
        account.updateToken(tokenStr);
    }

    public void changePassword(String oldPasswordStr,String newPasswordStr) {
        account.changePassword(oldPasswordStr, newPasswordStr);
    }

    public UserId getUserId() {
        return userId;
    }

    public UserName getUserName() {
        return userName;
    }

    public StatusEnum getStatus() {
        return status;
    }

    public Account getAccount() {
        return account;
    }

    public TenantId getTenantId() {
        return tenantId;
    }

    public List getRoleIds() {
        return roleIds;
    }
}

你可能感兴趣的:(DDD,DDD)