rabbit-obsidian/Clippings/DDD 04-什么是聚合根.md

11 KiB
Raw Permalink Blame History

title source author published created description tags
DDD | 04-什么是聚合根 https://www.cnblogs.com/dolphinmind/p/18303970
Neking
2024-07-15T21:04:00.0000000+08:00 2024-12-16 聚合根Aggregate Root是DDD中的一个核心概念用于组织和管理一组相关的领域对象确保它们的整体一致性和完整性
clippings

三、什么是聚合根?

聚合根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);    }}