2. 领域驱动设计核心概念:实体、值对象、聚合、领域服务、领域事件

领域驱动设计(DDD)提供了一套严谨的战术建模工具,用于将复杂的业务逻辑映射到代码结构。本章深入剖析五个核心构造块:实体、值对象、聚合、领域服务和领域事件。理解这些概念的区别与协作关系,是构建高内聚、低耦合领域模型的前提。

2.1 实体(Entity)

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

  • 唯一标识:通常使用 UUID、业务主键或数据库自增 ID。
  • 可变性:实体的属性可以随时间改变,但标识不变。
  • 业务行为:实体封装与其身份相关的业务逻辑,而非仅作为数据容器。
Go 示例:用户实体
type User struct {
    ID        string    // 唯一标识
    Name      string
    Email     string
    CreatedAt time.Time
}

func (u *User) ChangeEmail(newEmail string) error {
    // 业务规则:邮箱格式校验、唯一性检查等
    u.Email = newEmail
    return nil
}

2.2 值对象(Value Object)

值对象通过属性值来定义自身,没有唯一标识。两个值对象如果所有属性都相同,则视为同一个对象。值对象通常是不可变的,修改即替换。

对比维度 实体 值对象
标识 有唯一标识 无标识,靠值相等
可变性 可变 不可变(推荐)
生命周期 独立生命周期 依附于实体
相等性 标识相等 属性全相等
典型例子 用户、订单、产品 地址、金额、颜色
实践建议:在 Go 中,值对象通常不暴露指针接收器,而是返回新实例。优先使用值对象描述“是什么”,而非“是谁”。

2.3 聚合(Aggregate)

聚合是一组相关对象的一致性边界。聚合根(Aggregate Root)是外部访问聚合内对象的唯一入口。聚合保证边界内的业务规则始终一致。

  • 聚合根:一个实体,作为聚合的对外门面。
  • 一致性边界:聚合内的所有变更必须通过聚合根,确保事务一致性。
  • 引用规则:外部对象只能持有聚合根的 ID,不能持有内部对象的引用。
聚合边界与聚合根示例 订单聚合边界 订单(聚合根) OrderID: 唯一标识 订单项(实体) LineItemID, 数量 收货地址(值对象) 省/市/区/街道 客户(实体) CustomerID 支付记录(实体) PaymentID 外部只能通过聚合根 ID 引用 不能直接持有订单项或地址的引用
常见误区:不要将聚合设计得过大。一个聚合应只包含必须保持强一致性的对象。跨聚合的最终一致性通过领域事件实现。

2.4 领域服务(Domain Service)

当某个业务操作无法自然地归属于某个实体或值对象时,应将其建模为领域服务。领域服务是无状态的,它协调多个领域对象完成一个业务用例。

  • 无状态:领域服务不持有自己的状态,只编排逻辑。
  • 业务含义:方法名应体现业务意图,如 TransferFunds、CalculateDiscount。
  • 依赖注入:领域服务通常依赖仓储接口或其它领域服务。
Go 示例:转账领域服务
type TransferService struct {
    accountRepo AccountRepository
}

func (s *TransferService) Transfer(fromID, toID string, amount Money) error {
    from := s.accountRepo.FindByID(fromID)
    to := s.accountRepo.FindByID(toID)

    if err := from.Withdraw(amount); err != nil {
        return err
    }
    to.Deposit(amount)

    s.accountRepo.Save(from)
    s.accountRepo.Save(to)
    return nil
}

2.5 领域事件(Domain Event)

领域事件表示领域中已经发生的、对业务有意义的事实。它用于在聚合之间传递信息,实现最终一致性和事件溯源。

特性 说明
不可变性 事件一旦发布,内容不可更改
命名 使用过去时态,如 OrderSubmitted
包含信息 事件 ID、发生时间、业务数据
用途 解耦聚合、触发流程、审计日志
Go 示例:订单提交事件
type OrderSubmitted struct {
    EventID   string
    OrderID   string
    UserID    string
    Total     Money
    Timestamp time.Time
}

// 在聚合根中发布事件
func (o *Order) Submit() ([]interface{}, error) {
    // 业务校验...
    o.Status = OrderStatusSubmitted
    event := OrderSubmitted{
        EventID:   uuid.New().String(),
        OrderID:   o.ID,
        UserID:    o.UserID,
        Total:     o.Total,
        Timestamp: time.Now(),
    }
    return []interface{}{event}, nil
}
领域事件驱动流程 订单聚合 OrderSubmitted 事件总线 发布 / 订阅 库存聚合 扣减库存 通知聚合 发送确认邮件 审计聚合 记录操作日志

2.6 核心概念关系总结

这五个构造块共同构成了领域模型的骨架。实体和值对象是基本元素,聚合是一致性边界,领域服务处理跨对象逻辑,领域事件实现松耦合通信。

建模检查清单:
  • 有唯一标识且会变化?→ 实体
  • 只描述属性且不可变?→ 值对象
  • 需要保证一组对象的一致性?→ 聚合
  • 逻辑不属于任何实体/值对象?→ 领域服务
  • 需要通知其他部分“发生了什么”?→ 领域事件