2. 领域驱动设计(DDD)基础:实体、值对象、聚合、限界上下文的概念与作用

领域驱动设计(Domain-Driven Design,DDD)是应对复杂业务逻辑的核心方法论。它提供了一套精确的战术模式(实体、值对象、聚合)和战略模式(限界上下文),帮助架构师在业务系统中划定清晰的模块边界,并确保接口设计能够反映真实的业务语义。

核心认知: DDD 不是技术框架,而是一种建模思维。它要求团队以业务专家的语言(通用语言)来构建软件模型,从而避免技术实现与业务需求之间的鸿沟。

2.1 实体(Entity)

实体是具有唯一标识且其状态会随时间变化的对象。在业务系统中,一个实体必须能够被唯一地识别和追踪,即使它的所有属性都发生了变化,它仍然是同一个对象。

  • 核心特征: 拥有唯一标识(ID),如用户ID、订单ID、合同编号。
  • 可变性: 属性可以被修改,但身份不变。例如,用户修改了手机号,该用户实体依然是同一个用户。
  • 业务作用: 承载业务生命周期和状态流转,是业务操作的主要对象。
对比维度 实体(Entity) 值对象(Value Object)
标识 有唯一标识(ID) 无标识,由属性值定义
可变性 可变(Mutable) 不可变(Immutable)
相等性 通过 ID 判断 通过所有属性值判断
生命周期 有独立生命周期 依附于实体,无独立生命周期
典型示例 订单、客户、产品 地址、金额、时间段

2.2 值对象(Value Object)

值对象用于描述实体的属性或特征,它没有唯一标识,只通过其属性值来定义。值对象是不可变的,一旦创建就不能被修改;如果需要不同的值,则创建一个新的值对象。

  • 不可变性: 创建后属性不可变,保证了线程安全和逻辑一致性。
  • 无副作用: 对值对象的操作应返回新实例,而非修改自身。
  • 业务语义: 将多个相关属性封装为一个整体,提升模型表达力。例如,将“省、市、区、街道”封装为 Address 值对象。
// 值对象示例:金额
public class Money {
    private final BigDecimal amount;
    private final String currency;

    public Money(BigDecimal amount, String currency) {
        this.amount = amount;
        this.currency = currency;
    }

    // 加法操作返回新实例,而非修改自身
    public Money add(Money other) {
        if (!this.currency.equals(other.currency)) {
            throw new IllegalArgumentException("货币类型不匹配");
        }
        return new Money(this.amount.add(other.amount), this.currency);
    }

    // 通过所有属性判断相等性
    @Override
    public boolean equals(Object o) { /* ... */ }
    @Override
    public int hashCode() { /* ... */ }
}

2.3 聚合(Aggregate)

聚合是一组相关对象的集合,它将实体和值对象封装在一个一致性边界内。聚合通过一个聚合根(Aggregate Root)来对外暴露操作,外部对象只能通过聚合根来访问聚合内部的对象,从而保证数据的一致性和业务规则的不变性。

  • 聚合根: 聚合中唯一的实体,是外部访问聚合的唯一入口。例如,订单是聚合根,订单项是聚合内的实体。
  • 一致性边界: 聚合内部的所有变更必须在一次事务中完成,确保业务规则不被破坏。
  • 引用规则: 外部对象只能持有对聚合根的引用,不能直接引用聚合内部的实体。
设计原则: 聚合应该尽量小,只包含必须保持一致的业务对象。过大的聚合会导致事务冲突和性能问题。
聚合(Aggregate)结构示例:订单聚合 聚合边界 订单(聚合根) 订单ID | 下单时间 | 状态 订单项(实体) 商品ID | 数量 | 单价 收货地址(值对象) 省 | 市 | 区 | 详细地址 金额(值对象) 数值 | 货币类型 外部对象 只能引用聚合根

2.4 限界上下文(Bounded Context)

限界上下文是 DDD 战略设计的核心,它定义了模型应用的边界。在一个限界上下文内部,通用语言(Ubiquitous Language)是统一的,模型具有明确的含义;不同的限界上下文之间通过上下文映射(Context Map)进行集成。

  • 边界划分: 每个限界上下文对应一个独立的业务子域,拥有自己的模型和数据库。
  • 通用语言: 团队与业务专家使用统一的术语进行沟通,这些术语直接映射到代码中的模型。
  • 独立演进: 不同限界上下文可以独立开发、部署和扩展,降低耦合度。
常见误区: 不要试图创建一个“大一统”的全局模型。不同业务上下文对同一个概念的理解可能完全不同(例如,“客户”在销售上下文和售后上下文中的属性与行为差异很大),强行统一会导致模型臃肿且难以维护。
限界上下文(Bounded Context)与上下文映射 订单上下文 模型:订单、订单项 通用语言:下单、支付 数据库:订单库 库存上下文 模型:库存、仓库 通用语言:入库、出库 数据库:库存库 物流上下文 模型:运单、配送员 通用语言:发货、签收 数据库:物流库 上下文映射:防腐层(Anti-Corruption Layer) 订单上下文通过防腐层调用库存上下文,隔离模型差异,防止外部模型污染内部模型

2.5 战术模式与战略模式的关系

在 DDD 实践中,战略设计(限界上下文、上下文映射)决定了系统的宏观模块边界,而战术设计(实体、值对象、聚合、领域服务等)则指导了边界内部的微观模型实现。两者相辅相成,缺一不可。

设计层次 核心模式 关注点 产出物
战略设计 限界上下文、上下文映射 系统边界、团队组织、集成策略 上下文地图、模块划分
战术设计 实体、值对象、聚合、领域服务 业务逻辑、数据一致性、模型表达 领域模型、代码实现
实战建议: 在开始编码之前,先与业务专家一起绘制限界上下文地图,明确每个上下文的职责和交互方式。然后,在每个上下文内部,使用实体和值对象来构建领域模型,并通过聚合来保证业务规则的一致性。这种自上而下的设计方法,能够有效避免“大泥球”架构,为后续的接口版本管理奠定坚实的业务基础。