4. 领域驱动设计基础:实体、值对象、聚合、领域事件、限界上下文的核心概念

领域驱动设计(Domain-Driven Design,DDD)为复杂业务系统的建模提供了一套严谨的战术模式。在 Go 后端架构中,正确运用这些模式是划分服务边界、设计发布拓扑的前提。本章将逐一拆解 DDD 的五个核心构造块:实体、值对象、聚合、领域事件和限界上下文,并说明它们在 Go 代码中的典型映射。

4.1 实体(Entity)

实体是具有唯一标识且生命周期持续变化的对象。两个实体即使所有属性相同,只要 ID 不同,就是不同的对象。

  • 核心特征:拥有标识符(ID),可变(Mutable),通过 ID 进行相等性比较。
  • Go 实现要点:通常使用 struct 表示,ID 字段单独定义类型,避免与基础类型混淆。
Go 实体示例:
// UserId 是用户实体的标识符类型
type UserId string

type User struct {
    ID        UserId
    Name      string
    Email     string
    Version   int       // 乐观锁版本号
    CreatedAt time.Time
    UpdatedAt time.Time
}

// 通过 ID 比较两个实体是否相同
func (u *User) Equals(other *User) bool {
    return u.ID == other.ID
}

4.2 值对象(Value Object)

值对象通过属性值来定义自身,没有唯一标识,且通常是不可变(Immutable)的。两个值对象如果所有属性相同,则视为同一个对象。

  • 核心特征:无 ID、不可变、通过属性值比较、可被共享和替换。
  • Go 实现要点:使用 struct 且不暴露修改方法;通过 DeepEqual 或自定义 Equals 方法比较。
设计提示:在 Go 中,值对象通常不包含指针或引用类型字段,以确保值语义的纯粹性。对于金额、地址、坐标等概念,优先建模为值对象。
// Address 是一个典型的值对象
type Address struct {
    Province string
    City     string
    District string
    Detail   string
}

// 值对象比较:所有字段相等即视为相同
func (a Address) Equals(other Address) bool {
    return a.Province == other.Province &&
        a.City == other.City &&
        a.District == other.District &&
        a.Detail == other.Detail
}

4.3 聚合(Aggregate)

聚合是一组相关对象的一致性边界。外部对象只能通过聚合根(Aggregate Root)来访问聚合内部的实体和值对象。聚合保证了业务不变量的强一致性。

概念 说明 Go 映射
聚合根 聚合中唯一能被外部引用的实体 顶层 struct,包含全局唯一 ID
内部实体 属于聚合内部,只能通过聚合根访问 小写字段或包内私有 struct
值对象 聚合内部的属性集合 不可变 struct,嵌入或作为字段
一致性边界 事务范围内保证所有内部对象状态一致 Repository 以聚合为单位进行持久化
常见误区:不要将数据库表的一对多关系直接映射为聚合关系。聚合边界应由业务不变性决定,而非数据关联。过大的聚合会导致性能瓶颈和并发冲突。
// Order 是聚合根
type Order struct {
    ID        OrderId
    CustomerId UserId
    Items     []OrderItem  // 内部实体,不对外暴露直接修改
    Status    OrderStatus
    Version   int
}

// OrderItem 是聚合内部的实体,没有全局 ID
type OrderItem struct {
    ProductId string
    Quantity  int
    UnitPrice Money
}

// 通过聚合根的方法修改内部状态
func (o *Order) AddItem(productId string, qty int, price Money) error {
    // 业务校验:检查库存、重复商品等
    o.Items = append(o.Items, OrderItem{
        ProductId: productId,
        Quantity:  qty,
        UnitPrice: price,
    })
    return nil
}
聚合边界与聚合根结构 聚合边界(事务一致性) Order(聚合根) ID: OrderId | Status | Version OrderItem(内部实体) ProductId | Quantity Money(值对象) Amount | Currency 外部只能访问聚合根

4.4 领域事件(Domain Event)

领域事件是领域中发生的、业务人员关心的事实。它用于在聚合之间传递状态变更通知,实现最终一致性。在分布式系统中,领域事件是服务间异步协作的核心载体。

  • 特征:过去时命名(如 OrderCreated)、包含事件 ID 和发生时间、不可变。
  • Go 实现:通常定义为 struct,包含事件元数据和业务载荷。
// OrderCreated 领域事件
type OrderCreated struct {
    EventID    string    `json:"event_id"`
    OccurredAt time.Time `json:"occurred_at"`
    OrderID    string    `json:"order_id"`
    CustomerID string    `json:"customer_id"`
    TotalAmount float64  `json:"total_amount"`
}

// 在聚合根方法中发布事件
func (o *Order) Create() ([]interface{}, error) {
    // ... 业务逻辑 ...
    event := OrderCreated{
        EventID:    uuid.New().String(),
        OccurredAt: time.Now(),
        OrderID:    string(o.ID),
        CustomerID: string(o.CustomerId),
        TotalAmount: o.CalculateTotal(),
    }
    return []interface{}{event}, nil
}
发布策略:在 Go 后端中,领域事件通常在 Repository 的 Save 方法内,与聚合状态变更一起原子性地持久化到事件存储或消息队列中,避免“发事件”与“改状态”不一致。

4.5 限界上下文(Bounded Context)

限界上下文是 DDD 中最宏观的战略模式。它定义了模型应用的明确边界。在每个限界上下文内部,统一语言(Ubiquitous Language)和模型是自洽的;不同上下文之间的模型可以有不同的含义和结构。

维度 说明 Go 微服务映射
模型边界 每个上下文拥有独立的领域模型 独立的 Go module 或 package
统一语言 团队内部使用相同的术语沟通 代码中的命名与业务术语一致
通信方式 上下文之间通过事件或 API 协作 gRPC / HTTP / 消息队列
数据存储 每个上下文拥有独立的数据库或 schema 独立的数据库实例或表前缀
限界上下文与上下文映射 订单上下文 Order, OrderItem OrderRepository OrderService 数据库: orders_db 库存上下文 Product, Stock InventoryRepository InventoryService 数据库: inventory_db 支付上下文 Payment, Transaction PaymentRepository PaymentService 数据库: payment_db OrderCreated PaymentRequested

4.6 核心概念对比

下表总结了五个核心概念在标识、可变性、生命周期和比较方式上的关键差异:

概念 唯一标识 可变性 生命周期 相等性比较
实体 有(ID) 可变 持续变化 通过 ID
值对象 无 不可变 无(瞬态) 通过所有属性
聚合 聚合根有 ID 内部可变,外部不可见 事务边界内一致 通过聚合根 ID
领域事件 有(EventID) 不可变 发布即完成 通过 EventID
限界上下文 有(上下文名称) 模型独立演进 团队/服务生命周期 不适用