2. 领域驱动设计基础:实体与值对象、聚合与聚合根、领域服务、领域事件、限界上下文

在构建 GoBFF 这类面向 B 端复杂业务的前后端分离架构时,领域驱动设计(DDD)提供了从业务模型到代码模型的核心映射方法。本章聚焦 DDD 的六大基础构造块,这些构造块是后续实现领域边界发布与回滚的基石。

2.1 实体与值对象

实体与值对象是领域模型中最基本的两种对象类型,它们的区别决定了对象在系统中的生命周期与标识方式。

特性 实体(Entity) 值对象(Value Object)
标识 拥有唯一标识(ID),标识贯穿生命周期 无标识,由属性值定义相等性
可变性 通常可变,状态随时间变化 不可变,替换而非修改
生命周期 独立生命周期,可被追踪 依附于实体,无独立生命周期
相等性 通过 ID 判断 通过所有属性值判断
Go 实现示例 type User struct { ID string; Name string } type Address struct { City, Street string }
实践建议:在 GoBFF 中,Order 是实体(有订单号),Money 是值对象(金额+币种)。值对象应实现 Equals() 方法,实体应实现 SameIdentityAs() 方法。
实体与值对象对比 实体(Entity) User{ID: "u-001", Name: "张三"} Order{ID: "ord-2024-001", Status: "paid"} 标识:UUID / 自增ID / 业务编号 可变:状态可更新 相等:ID 相同即同一对象 Go:struct + ID 字段 值对象(Value Object) Money{Amount: 99.00, Currency: "CNY"} Address{City: "北京", Street: "朝阳路"} 无标识:属性决定相等 不可变:修改时返回新实例 相等:所有字段值相同 Go:struct + Equals() 方法

2.2 聚合与聚合根

聚合是领域模型的边界单元,它保证内部数据的一致性。聚合根是聚合中唯一能被外部访问的实体。

  • 聚合根(Aggregate Root):聚合的入口点,外部只能通过聚合根操作聚合内的对象。
  • 聚合边界(Aggregate Boundary):定义了事务一致性的范围,一个事务只修改一个聚合。
  • 内部对象:聚合内的实体和值对象只能通过聚合根访问,不能持有外部引用。
GoBFF 聚合设计原则:
  • 聚合根使用 interface 定义公共方法,隐藏内部实现。
  • 聚合内对象通过 private 字段或 internal 包控制访问。
  • 跨聚合通信使用领域事件,避免直接引用。
聚合与聚合根结构 聚合边界(事务一致性) 聚合根:Order ID: string | Status: OrderStatus | Items: []OrderItem OrderItem(实体) ProductID | Quantity | Price ShippingAddress(值对象) City | Street | ZipCode 外部只能通过聚合根访问

2.3 领域服务

当某个领域操作不适合放在实体或值对象中时,领域服务(Domain Service)作为无状态的协调者出现。它处理跨聚合的业务逻辑或依赖外部资源的计算。

  • 无状态:领域服务不持有自身状态,所有状态来自参数或仓储。
  • 单一职责:一个领域服务只解决一个领域问题。
  • 命名规范:通常以动词或动词短语命名,如 OrderFulfillmentService。
避免滥用:不要将本应属于实体或值对象的方法抽取到领域服务中。领域服务只处理“跨对象”或“依赖外部”的逻辑。
// Go 领域服务示例
type OrderFulfillmentService struct {
    inventoryRepo InventoryRepository
    paymentGateway PaymentGateway
}

func (s *OrderFulfillmentService) FulfillOrder(ctx context.Context, order *Order) error {
    // 1. 检查库存(跨聚合)
    if !s.inventoryRepo.HasStock(ctx, order.Items) {
        return ErrInsufficientStock
    }
    // 2. 执行支付(依赖外部网关)
    if err := s.paymentGateway.Charge(ctx, order.TotalAmount); err != nil {
        return err
    }
    // 3. 更新订单状态
    order.MarkAsFulfilled()
    return nil
}

2.4 领域事件

领域事件(Domain Event)是聚合之间解耦通信的核心机制。当聚合内发生重要状态变更时,发布事件供其他聚合或限界上下文消费。

要素 说明 Go 实现要点
事件名称 过去时动词,如 OrderPlaced type OrderPlaced struct { ... }
事件数据 只包含必要信息,避免传递整个聚合 字段:OrderID, PlacedAt, CustomerID
事件时间戳 记录事件发生时间 OccurredAt time.Time
发布机制 同步或异步,推荐异步解耦 使用消息队列或事件总线
GoBFF 事件流:订单服务发布 OrderPlaced 事件 → 库存服务消费并扣减库存 → 通知服务发送确认邮件。每个服务只关心自己领域内的事件。

2.5 限界上下文

限界上下文(Bounded Context)是 DDD 中最宏观的边界划分。每个限界上下文拥有自己的领域模型、数据库和团队,上下文之间通过防腐层(ACL)或开放主机服务(OHS)通信。

  • 独立模型:同一个概念在不同上下文中可以有不同含义。例如“用户”在订单上下文中是 Customer,在支付上下文中是 Payer。
  • 通信方式:上下文映射(Context Map)定义了集成模式,如合作伙伴、共享内核、客户-供应商等。
  • GoBFF 实践:每个 BFF 服务对应一个限界上下文,拥有独立的 API 网关和数据存储。
限界上下文与上下文映射 订单上下文 模型:Order, OrderItem 数据库:order_db API:/api/orders 团队:订单组 库存上下文 模型:Product, Stock 数据库:inventory_db API:/api/inventory 团队:库存组 支付上下文 模型:Payment, Transaction 数据库:payment_db API:/api/payments 团队:支付组 事件 事件 防腐层(ACL):转换模型,隔离变化

2.6 六大构造块关系总结

这六大构造块共同构成了 DDD 的建模语言。在 GoBFF 中,它们被映射为以下代码结构:

DDD 构造块 Go 代码映射 BFF 中的角色
实体 struct + ID 字段 业务核心对象,如订单、用户
值对象 struct + Equals() 金额、地址、时间范围
聚合根 struct + 公共方法 事务边界入口
领域服务 struct + 方法 跨聚合业务编排
领域事件 struct + 事件总线 异步解耦通信
限界上下文 module / package 微服务边界
核心原则:限界上下文是最大的边界,聚合是事务边界,实体和值对象是模型的最小单元。领域服务协调跨聚合操作,领域事件实现跨上下文通信。这六大构造块在 GoBFF 中直接决定了代码的组织方式和发布回滚的粒度。