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

领域驱动设计(Domain-Driven Design,DDD)由 Eric Evans 于 2003 年提出,是一套应对复杂业务建模的方法论。在微服务架构中,DDD 提供了将业务边界映射为服务边界的核心工具。本节将逐一解析 DDD 的五个核心构造块,并阐明它们在微服务通信中的角色。

2.1 实体(Entity)

定义:实体是具有唯一标识且在其生命周期内状态可变的领域对象。两个实体即使所有属性值相同,只要标识不同,就是不同的对象。

特征 说明 Go 实现要点
唯一标识 ID 不可变,用于区分对象 使用 string 或 uuid.UUID 类型
可变状态 属性可随时间变化 通过方法修改内部字段
生命周期 从创建到持久化再到删除 仓储(Repository)负责存取
相等性 基于 ID 而非属性值 实现 Equals() 方法比较 ID
微服务通信视角:实体是跨服务传递业务状态的最小单元。在事件驱动架构中,实体 ID 是事件负载中的关键字段,用于接收方定位或重建实体。
// Go 实体示例:订单实体
type Order struct {
    ID         string    `json:"id"`
    CustomerID string    `json:"customer_id"`
    Items      []OrderItem `json:"items"`
    Status     string    `json:"status"`
    Version    int       `json:"version"` // 乐观锁版本
}

func (o *Order) ChangeStatus(newStatus string) error {
    // 状态校验逻辑
    o.Status = newStatus
    return nil
}

func (o *Order) Equals(other *Order) bool {
    return o.ID == other.ID
}

2.2 值对象(Value Object)

定义:值对象通过属性值来定义自身,没有唯一标识,且一旦创建就不可变。两个值对象如果所有属性值相同,则视为相等。

特征 说明 Go 实现要点
无标识 完全由属性值定义 不定义 ID 字段
不可变 创建后属性不可修改 字段导出为只读或使用 getter
可替换 修改时创建新实例 返回新值对象而非修改原对象
相等性 基于所有属性值 实现 Equals() 逐字段比较
最佳实践:在 Go 中,值对象通常定义为结构体,字段首字母小写(包内私有),通过构造函数创建。事件负载中的金额、地址、时间范围等天然适合建模为值对象。
// Go 值对象示例:金额
type Money struct {
    amount   float64
    currency string
}

func NewMoney(amount float64, currency string) Money {
    return Money{amount: amount, currency: currency}
}

func (m Money) Amount() float64    { return m.amount }
func (m Money) Currency() string   { return m.currency }

func (m Money) Add(other Money) (Money, error) {
    if m.currency != other.currency {
        return Money{}, errors.New("currency mismatch")
    }
    return NewMoney(m.amount+other.amount, m.currency), nil
}

func (m Money) Equals(other Money) bool {
    return m.amount == other.amount && m.currency == other.currency
}

2.3 聚合(Aggregate)

定义:聚合是一组相关实体和值对象的集群,由一个聚合根(Aggregate Root)统一对外访问。聚合保证了内部数据的一致性和不变性,是事务一致性的边界。

聚合结构:订单聚合示例 聚合根:Order(实体) ID, CustomerID, Status, Version OrderItem(实体) ProductID, Quantity, Price Money(值对象) Amount, Currency 外部只能通过聚合根访问内部对象 保证数据一致性和不变性 事务一致性边界 一个事务内只修改一个聚合,跨聚合使用最终一致性
聚合设计原则:
  • 聚合要尽量小,只包含必须保持一致的实体和值对象
  • 聚合根通过全局唯一 ID 引用,内部对象通过本地 ID 引用
  • 跨聚合的修改必须使用领域事件实现最终一致性
  • 一个事务只修改一个聚合

2.4 领域事件(Domain Event)

定义:领域事件是领域中发生的、对业务有意义的、过去时的事实记录。它是聚合之间、限界上下文之间异步通信的核心载体。

属性 说明 示例值
EventID 事件唯一标识 evt_20250321_001
AggregateID 产生事件的聚合根 ID order_98765
EventType 事件类型名称 OrderSubmitted
OccurredOn 事件发生时间戳 2025-03-21T10:30:00Z
Payload 事件携带的业务数据 JSON 序列化的值对象
// Go 领域事件示例
type OrderSubmittedEvent struct {
    EventID    string    `json:"event_id"`
    OrderID    string    `json:"order_id"`
    CustomerID string    `json:"customer_id"`
    Total      Money     `json:"total"`
    OccurredOn time.Time `json:"occurred_on"`
}

// 聚合根中发布事件
func (o *Order) Submit() ([]interface{}, error) {
    if o.Status != "pending" {
        return nil, errors.New("order must be pending to submit")
    }
    o.Status = "submitted"
    event := OrderSubmittedEvent{
        EventID:    uuid.New().String(),
        OrderID:    o.ID,
        CustomerID: o.CustomerID,
        Total:      o.calculateTotal(),
        OccurredOn: time.Now().UTC(),
    }
    return []interface{}{event}, nil
}

2.5 限界上下文(Bounded Context)

定义:限界上下文是 DDD 中最关键的战术模式,它划定了模型应用的明确边界。每个限界上下文拥有自己的统一语言(Ubiquitous Language),内部模型对外部上下文不透明。

限界上下文与微服务映射 订单上下文 Order(实体) OrderItem(实体) Money(值对象) 统一语言:订单、下单、取消 库存上下文 Product(实体) Warehouse(实体) StockLevel(值对象) 统一语言:入库、出库、盘点 支付上下文 Payment(实体) Transaction(实体) Currency(值对象) 统一语言:支付、退款、对账 上下文间通过领域事件或 API 进行通信 每个上下文独立部署为微服务,拥有自己的数据库 微服务边界 = 限界上下文边界

2.6 核心概念关系总结

概念 标识 可变性 事务边界 通信角色
实体 有唯一 ID 可变 聚合内 事件发布者/订阅者
值对象 无 ID 不可变 聚合内 事件负载数据
聚合 聚合根 ID 聚合根可变 事务边界 事件源
领域事件 事件 ID 不可变 跨聚合 异步消息
限界上下文 上下文名称 模型边界 服务边界 微服务单元
微服务架构映射:
  • 一个限界上下文 = 一个微服务
  • 聚合根 = 微服务中的核心业务对象
  • 领域事件 = 微服务间异步通信的消息
  • 值对象 = 事件负载中的数据传输对象(DTO)
  • 实体 ID = 消息路由和关联的关键字段