首页 > 其他分享 >DDD | 04-什么是聚合根

DDD | 04-什么是聚合根

时间:2024-07-15 21:08:13浏览次数:10  
标签:聚合 04 实体 public 订单 return ID DDD

三、什么是聚合根?

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

标签:聚合,04,实体,public,订单,return,ID,DDD
From: https://www.cnblogs.com/dolphinmind/p/18303970

相关文章

  • DDD | 02-值对象拓展示例
    示例拓展金额和货币创建一个表示金额和货币的值对象(AmountVO),在系统中统一处理货币相关的数据,确保精度和一致性。importjava.math.BigDecimal;importjava.util.Currency;/***这个AmountVO类使用BigDecimal来精确存储金额值,避免了浮点运算可能带来的精度问题。同时,利用C......
  • (137)SRAM接口--->(004)基于FPGA实现SRAM接口
    1目录(a)FPGA简介(b)IC简介(c)Verilog简介(d)基于FPGA实现SRAM接口(e)结束1FPGA简介(a)FPGA(FieldProgrammableGateArray)是在PAL(可编程阵列逻辑)、GAL(通用阵列逻辑)等可编程器件的基础上进一步发展的产物。它是作为专用集成电路(ASIC)领域中的一种半定制电路而出现的,既解决了定制电......
  • Ubuntu 20.04安装cuckoo sandbox
    前言:沙盒(Sanbox)是一种将未知、不可信的软件隔离执行的安全机制。恶意软件分析沙盒一般用来将不可信软件放在隔离环境中自动地动态执行,然后提取其运行过程中的进程行为、网络行为、文件行为等动态行为,安全研究员可以根据这些行为分析结果对恶意软件进行更深入地分析。课程有一......
  • javaSE学习 day04
    目录1.数组1.1数组是什么1.2静态数组1.2.1数组的格式1.2.2数组的访问1.2.3获取数组的长度1.3动态数组1.3.1动态数组是什么1.3.2动态数组的格式 1.3.3默认值规则1.4数组的遍历1.4.1什么是数组的遍历1.4.2为什么要遍历1.4.3遍历的格式1.5综合案例1.5.1计算班级......
  • P3304 [SDOI2013] 直径
    原题链接题解先随便找一条直径,然后标记这些边,然后看看直径上的点有没有不需要经过标记边的路径,使得其长度等于该点到直径端点的路径长度code#include<bits/stdc++.h>#definelllonglongusingnamespacestd;structedge{llto,val;};vector<edge>G[200005];l......
  • ORA-01092 ORA-00604 ORA-08103故障处理---惜分飞
    联系:手机/微信(+8617813235971)QQ(107644445)标题:ORA-01092ORA-00604ORA-08103故障处理作者:惜分飞©版权所有[未经本人同意,不得以任何形式转载,否则有进一步追究法律责任的权利.]数据库启动报SQL>alterdatabaseopen;alterdatabaseopen*第1行出现错误:O......
  • 全志科技官方Ubuntu16.04根文件系统镜像的替换和测试方法
    本文主要基于全志A40i开发板——TLA40i-EVM,一款基于全志科技A40i处理器设计的4核ARMCortex-A7高性能低功耗国产评估板,演示Ubuntu根文件系统镜像的替换和测试方法。创龙科技TLA40i-EVM评估板接口资源丰富,引出双路网口、双路CAN、双路USB、双路RS485等通信接口,板载Bluetooth、WIFI......
  • P8704 [蓝桥杯 2020 省 A1] 填空问题 题解
    题目传送门A.跑步训练我们经过仔细观察,可以发现每222分钟就会消耗300300......
  • Ubuntu 18.04安装docker记录
    Docker简介Docker是一个开源的应用容器引擎,可以让开发者打包应用以及依赖包到一个轻量级、可移植的容器中,然后发布到Linux服务器上。Docker安装步骤检查卸载老版本的docker,Ubuntu可能自带低版本的docker,需要先卸载再安装新的版本的:sudoapt-getremovedockerdocker-e......
  • ubuntu20.04离线部署ceph集群
    版本兼容:查看ceph和系统的版本是否兼容节点说明ceph-admin:192.168.83.133ceph节点IPDomainHostnameServices192.168.83.133stor01.kb.cxceph01mon,mgr,mds192.168.83.134stor02.kb.cxceph02mgr,mon,mds192.168.83.135stor03.kb.cxceph03osd,m......