1. 领域驱动设计(DDD)基础:实体、值对象、聚合、领域事件、限界上下文概念

领域驱动设计(Domain-Driven Design,简称 DDD)是一种软件设计方法论,强调以业务领域为核心,通过通用语言(Ubiquitous Language)将业务概念精确映射到代码模型中。在构建复杂业务系统时,DDD 提供了一套清晰的概念边界和建模工具,帮助团队避免技术实现与业务需求之间的鸿沟。

本章将逐一拆解 DDD 的五个核心构造块:实体、值对象、聚合、领域事件和限界上下文。理解这些概念是后续进行业务回放与领域建模的基石。

1.1 实体(Entity)

实体是具有唯一标识且生命周期持续变化的对象。两个实体即使所有属性值相同,只要标识不同,就是不同的对象。实体的核心特征是“身份”,而非属性。

  • 唯一标识:通常使用 ID(UUID、自增主键、业务主键)来区分。
  • 可变性:实体的属性可以随时间变化,但身份不变。
  • 业务行为:实体封装了与其身份相关的业务逻辑。
Go 实践提示:在 Go 中,实体通常定义为 struct,并显式包含 ID 字段。避免使用指针作为标识比较的依据,应始终通过 ID 方法或字段判断相等性。
// 实体示例:用户
type User struct {
    ID        string    // 唯一标识
    Name      string    // 可变属性
    Email     string    // 可变属性
    CreatedAt time.Time
}

// 通过 ID 判断实体相等性
func (u *User) Equals(other *User) bool {
    return u.ID == other.ID
}

1.2 值对象(Value Object)

值对象通过属性值来定义自身,没有唯一标识,且通常是不可变的。两个值对象如果所有属性都相同,则视为同一个对象。值对象用于描述实体的特征或业务中的度量概念。

  • 无标识:只靠属性值区分。
  • 不可变性:创建后属性不可修改,修改即创建新对象。
  • 可替换性:可以自由替换为另一个属性相同的值对象。
  • 自包含逻辑:值对象可以包含与其值相关的业务校验或计算。
对比维度 实体(Entity) 值对象(Value Object)
核心特征 唯一标识 属性值
可变性 可变 不可变
相等性判断 通过 ID 通过所有属性
生命周期 独立存在,持续变化 依附于实体,可被替换
典型例子 用户、订单、商品 金额、地址、颜色、时间段
// 值对象示例:金额
type Money struct {
    amount   decimal.Decimal
    currency string
}

// 不可变:返回新对象而非修改原对象
func (m Money) Add(other Money) (Money, error) {
    if m.currency != other.currency {
        return Money{}, errors.New("currency mismatch")
    }
    return Money{
        amount:   m.amount.Add(other.amount),
        currency: m.currency,
    }, nil
}

// 通过所有属性判断相等
func (m Money) Equals(other Money) bool {
    return m.amount.Equal(other.amount) && m.currency == other.currency
}

1.3 聚合(Aggregate)

聚合是一组相关对象的一致性边界,它将实体和值对象组织成一个整体,对外只通过一个聚合根(Aggregate Root)进行访问。聚合保证了边界内的数据变更始终满足业务不变条件(Invariant)。

  • 聚合根:聚合中唯一的实体,外部只能通过聚合根操作聚合内的对象。
  • 一致性边界:聚合内的所有变更必须在一次事务中完成。
  • 引用规则:聚合之间通过聚合根 ID 引用,而非直接持有对象引用。
聚合设计原则:
  1. 聚合要尽量小,只包含必须保持一致的属性。
  2. 聚合根负责维护不变条件。
  3. 跨聚合的修改应通过领域事件异步协调。
// 聚合根:订单
type Order struct {
    ID          string
    customerID  string        // 引用其他聚合的 ID
    items       []OrderItem   // 聚合内的实体
    totalAmount Money         // 聚合内的值对象
    status      OrderStatus
}

// 聚合内的实体:订单项
type OrderItem struct {
    productID string
    quantity  int
    price     Money
}

// 聚合根方法:保证不变条件
func (o *Order) AddItem(productID string, qty int, price Money) error {
    if o.status != OrderStatusPending {
        return errors.New("only pending order can add items")
    }
    // 业务校验:数量必须为正
    if qty <= 0 {
        return errors.New("quantity must be positive")
    }
    o.items = append(o.items, OrderItem{
        productID: productID,
        quantity:  qty,
        price:     price,
    })
    // 重新计算总金额
    o.recalculateTotal()
    return nil
}

1.4 领域事件(Domain Event)

领域事件表示领域中已经发生的、对业务有意义的事实。它用于解耦聚合之间的通信,将副作用从当前事务中分离出来,实现最终一致性。

  • 过去时命名:如 OrderCreated、PaymentReceived。
  • 不可变:事件一旦发布,内容不可更改。
  • 携带数据:包含事件发生时相关的上下文信息。
  • 异步处理:通常通过消息队列或事件总线分发。
// 领域事件定义
type OrderCreated struct {
    OrderID    string
    CustomerID string
    Total      Money
    CreatedAt  time.Time
}

// 在聚合根方法中发布事件
func (o *Order) Confirm() ([]interface{}, error) {
    if o.status != OrderStatusPending {
        return nil, errors.New("order already confirmed")
    }
    o.status = OrderStatusConfirmed
    // 返回领域事件,由基础设施层负责发布
    return []interface{}{
        OrderCreated{
            OrderID:    o.ID,
            CustomerID: o.customerID,
            Total:      o.totalAmount,
            CreatedAt:  time.Now(),
        },
    }, nil
}

1.5 限界上下文(Bounded Context)

限界上下文是 DDD 中最宏观的边界概念。它将一个大型业务系统拆分为多个语义上自治的子领域,每个子领域拥有自己的通用语言、模型和实现。不同限界上下文之间的模型可以不同,甚至同名概念在不同上下文中含义也不同。

限界上下文 核心职责 “用户”模型含义 “订单”模型含义
订单上下文 订单创建、支付、发货 买家(含地址、信用等级) 核心实体,包含商品明细、金额、状态
库存上下文 商品库存管理、出入库 仓库管理员 仅包含 SKU 和数量,无金额概念
营销上下文 优惠券、促销活动 目标用户群体(标签化) 不直接处理订单,只关心订单金额范围
常见误区:不要试图为整个系统建立一个统一的“完美模型”。限界上下文允许甚至鼓励不同上下文对同一概念有不同的定义。跨上下文的模型映射通过防腐层(Anti-Corruption Layer)处理。

1.6 核心概念关系总览

下图展示了五个核心概念在 DDD 分层架构中的位置与协作关系。限界上下文是最大的边界,内部包含多个聚合;聚合由聚合根(实体)和内部的值对象、实体组成;聚合之间的状态变更通过领域事件进行异步通知。

DDD 核心概念关系图 限界上下文 A(订单上下文) 聚合:订单 聚合根:Order 实体:OrderItem 值对象:Money 值对象:Address 聚合:客户 聚合根:Customer 值对象:Contact 领域事件:OrderCreated 限界上下文 B(库存上下文) 聚合:库存 聚合根:Inventory 实体:StockItem 值对象:Quantity 事件发布/订阅 关键规则 • 聚合之间通过 ID 引用,不直接持有对象 • 跨上下文通过领域事件异步通信 • 每个上下文拥有独立的通用语言 • 聚合内保证事务一致性

1.7 本章小结

DDD 的五个核心概念构成了业务建模的完整工具链:

  • 实体 负责有身份的业务主体,值对象 负责无身份的业务描述。
  • 聚合 通过聚合根封装一致性边界,领域事件 实现跨聚合的异步解耦。
  • 限界上下文 从宏观上划分业务领域,确保每个模型在其边界内自洽。
Go 工程落地要点:
  • 实体和值对象使用 struct 定义,值对象建议不暴露指针修改方法。
  • 聚合根方法返回事件切片,由应用层负责发布,保持领域层纯净。
  • 限界上下文对应 Go 中的独立 package 或 module,通过接口依赖倒置。