DDD | 04-什么是聚合根

三、什么是聚合根?

聚合根(Aggregate Root)是DDD中的一个核心概念,用于组织和管理一组相关的领域对象,确保它们的整体一致性和完整性。聚合根是领域模型中的关键组件,它不仅封装了领域内的复杂业务逻辑,还提供了控制访问和维护数据一致性的机制,是构建可维护、可扩展的软件系统的重要基石。

主要特点

  • 聚合的概念:聚合是一组相关对象的集合,这些对象一起形成一个完整的业务概念,并作为一个单元进行操作。在聚合内部,对象之间有严格的关联和依赖关系。
  • 根实体:聚合根是聚合中的一个特殊实体,它是外部世界访问聚合内部其他成员的唯一入口点。这意味着外部对象不能直接持有聚合内非根实体的引用,必须通过根实体来访问和操作它们。
  • 边界定义:每个聚合都有一个清晰的边界,这个边界定义了聚合的范围,以及哪些操作可以在聚合内部执行,哪些操作需要通过根实体来协调。这有助于维护数据的一致性和完整性。
  • 一致性保证:聚合根负责维护其内部的所有业务规则和一致性约束。当外部尝试修改聚合状态时,所有必要的验证和业务逻辑都在聚合根内部执行,确保操作的原子性和一致性。
  • 唯一标识:聚合根拥有一个全局唯一的标识符(ID),外部系统通过这个ID 来识别和访问聚合。这个ID也是与其他聚合建立关联的基础。
  • 生命周期管理:聚合根还负责管理器内部对象的生命周期,包括创建、更新和删除聚合内的实体和值对象。

设计原则

明确的边界

  • 聚合根定义了一个清晰的边界,限定那些对象属于聚合内部,那些属于外部。边界内的对象作为一个整体处理,对外部隐藏内部细节

单一入口点

  • 聚合根是外界访问聚合内部其他对象的唯一合法途径。外部对象不能直接持有或操作聚合内的非根实体,所有交互都需通过根实体的方法进行

内聚性

  • 聚合内的所有对象应紧密相关,共同完成一个业务功能。这意味着聚合内的对象和操作应该围绕着一个核心业务概念组织

一致性规则封装

  • 聚合根负责维护其内部的一致性,包括业务规则、验证逻辑等。所有改变聚合状态的操作都应该在根实体中实现,确保操作的原子性和业务规则的遵守

标识唯一性

  • 每个聚合根都有一个全局唯一的标识符(ID), 用以区分不同的聚合实例。这个ID 也用于外部对聚合的引用

生命周期管理

  • 聚合根控制其内部成员(实体和值对象)的生命周期,包括它们的创建、更新和删除

有限的大小

  • 为了保持聚合的可管理和易于理解,通常建议聚合的大小不要过大。过大的聚合可能导致性能问题和复杂度增加

聚合内部引用

  • 聚合内部的对象可以通常引用相互协作,但这些引用应限制在聚合边界之内。对合聚合间的关联,通常适用ID进行间接引用

事务边界

  • 在事务处理中,聚合根常常作为事务的边界,确保事务内的所有操作要么全部成功,要么全部失败,以此来维护数据的完整性

问题探讨

聚合根和实体对象有什么区别?

身份标识(Identity)

  • 实体(Entity):实体具有唯一标识(ID),这个ID用来区分领域中的每一个单独的实体实例。实体的ID在整个系统范围内具有唯一性
  • 聚合根 (Aggregate Root):聚合根也是一个实体,但它在一个聚合中扮演领导角色。它的ID不仅在系统范围内是唯一的,而且是外部世界访问聚合内部其他实体的入口点

聚合边界(Aggregate Boundary)

  • 实体:实体可以是聚合内部的一部分,也可以是独立存在的。如果实体是聚合内部的一部分,则其ID在聚合内部唯一即可,外部访问该实体必须通过聚合根
  • 聚合根:定义了聚合的边界,决定了那些对象属于聚合内部。外部对象不能直接访问聚合内的非根实体,只能通过聚合根暴露的接口进行操作

职责与控制

  • 实体:实体主要负责维护自身的状态和行为,可能包含一些简单的业务逻辑
  • 聚合根:负责维护整个聚合的一致性,包括内部实体的状态更改和业务规则的执行。它控制着对聚合内部元素的所有修改,确保在任何时刻聚合都处于有效状态

生命周期关联

  • 实体:实体的生命周期通常由其所在聚合根或外部服务(如Repository)管理
  • 聚合根:除了管理自己的生命周期外,还间接管理其内部实体的生命周期,决定它们的创建、更新和删除

访问控制

  • 实体:如果不是聚合根,实体通常不直接暴露给外部,外部组件不应直接持有实体的引用
  • 聚合根:是外部访问聚合内部的唯一合法通道,提供对外接口,隐藏内部实现细节和复杂性

综上所述,聚合根是一种特殊的实体,它不仅代表一个独立的业务概念,还负责协调和保护其内部的其他实体和值对象,确保整体的业务规则得到执行,维持数据的一致性和完整性。实体则是领域模型中的基本构建块,标识具有唯一标识的领域对象,而聚合根在实体之上提供了一层额外的结构和控制。

你觉得User是一个实体对象还是聚合根?

在大多数情况下,User 通常被设计为一个聚合根,因为它能更好地适应业务需求的变化和复杂性。

  • 唯一标识符:User拥有一个全局唯一的标识符(如用户ID),这满足实体的基本特征
  • 业务操纵的中心:用户账户是许多业务操作的中心,如登陆、资料编辑、权限管理等。这些操作往往涉及到用户信息的修改和验证,因此需要一个统一的入口点来维护这些操作的一致性和安全性,这正是聚合根的作用
  • 关联管理:用户可能与其他领域对象关联,比如用户可能拥有多个地址、多个角色或者与多个订单相关联。作为聚合根,User 可以管理这些关联关系,控制对这些关联对象的访问和修改,确保数据的完整性和一致性
  • 权限和安全:在很多系统中,用户数据是非常敏感的,需要严格控制访问权限。通过User 设计为聚合根,可以更集中地实施安全策略和访问控制逻辑

代码示例

设计一个订单聚合类(OrderAggregate)


/**
* 订单聚合类,封装了订单的相关信息和行为。
* 包括订单ID、顾客ID、订单状态、订单项、支付状态等。
*/
public class OrderAggregate {
    private final UUID orderId;
    private final UUID customerId;
    private OrderStatusVO status;
    private final List<OrderLineItemEntity> lineItems;
    private boolean isPaid;

    /**
     * 构造函数初始化订单聚合体。
     * @param orderId 订单ID
     * @param customerId 顾客ID
     * @param status 订单初始状态
     */
    public OrderAggregate(UUID orderId, UUID customerId, OrderStatusVO status) {
        this.orderId = orderId;
        this.customerId = customerId;
        this.status = status;
        this.lineItems = new ArrayList<>();
        this.isPaid = false;
    }

    /**
     * 添加订单项。
     * 如果订单已支付,则抛出IllegalStateException异常。
     * @param lineItem 要添加的订单项
     */
    public void addLineItem(OrderLineItemEntity lineItem) {
        if (!isPaid) {
            lineItems.add(lineItem);
        } else {
            throw new IllegalStateException("Cannot add line item to paid order");
        }
    }

    /**
     * 移除订单项。
     * @param lineItem 要移除的订单项
     */
    public void removeLineItem(OrderLineItemEntity lineItem) {
        lineItems.remove(lineItem);
    }

    /**
     * 标记订单为已支付。
     * 如果订单已支付,则抛出IllegalStateException异常。
     */
    public void pay() {
        if (!isPaid) {
            isPaid = true;
            status = OrderStatusVO.PAID;
        } else {
            throw new IllegalStateException("Order is already paid");
        }
    }

    /**
     * 更改订单状态。
     * @param newStatus 新的订单状态
     */
    public void changeStatus(OrderStatusVO newStatus) {
        this.status = newStatus;
    }

    /**
     * 计算订单的总金额。
     * @return 订单的总金额
     */
    public BigDecimal calculateTotalAmount() {
        return lineItems.stream()
                .map(OrderLineItemEntity::calculateTotalPrice)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    /**
     * 获取订单ID。
     * @return 订单ID
     */
    public UUID getOrderId() {
        return orderId;
    }

    /**
     * 获取顾客ID。
     * @return 顾客ID
     */
    public UUID getCustomerId() {
        return customerId;
    }

    /**
     * 获取订单状态。
     * @return 订单状态
     */
    public OrderStatusVO getStatus() {
        return status;
    }

    /**
     * 获取订单项列表。
     * @return 订单项列表
     */
    public List<OrderLineItemEntity> getLineItems() {
        return lineItems;
    }

    /**
     * 检查订单是否已支付。
     * @return 如果订单已支付返回true,否则返回false
     */
    public boolean isPaid() {
        return isPaid;
    }

    /**
     * 返回订单聚合体的字符串表示。
     * @return 订单聚合体的字符串表示
     */
    @Override
    public String toString() {
        return "OrderAggregate{" +
                "orderId=" + orderId +
                ", customerId=" + customerId +
                ", status=" + status +
                ", lineItems=" + lineItems +
                ", isPaid=" + isPaid +
                '}';
    }

    /**
     * 比较两个订单聚合体是否相等。
     * @param o 要比较的订单聚合体
     * @return 如果两个订单聚合体相等返回true,否则返回false
     */
    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }

        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        OrderAggregate that = (OrderAggregate) o;

        return isPaid == that.isPaid && Objects.equals(orderId, that.orderId) && Objects.equals(customerId, that.customerId) && status == that.status && Objects.equals(lineItems, that.lineItems);
    }

    /**
     * 计算订单聚合体的哈希码。
     * @return 订单聚合体的哈希码
     */
    @Override
    public int hashCode() {
        return Objects.hash(orderId, customerId, status, lineItems, isPaid);
    }
}

posted @ 2024-07-15 21:04  Neking  阅读(21)  评论(0编辑  收藏  举报