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() 方法。
2.2 聚合与聚合根
聚合是领域模型的边界单元,它保证内部数据的一致性。聚合根是聚合中唯一能被外部访问的实体。
- 聚合根(Aggregate Root):聚合的入口点,外部只能通过聚合根操作聚合内的对象。
- 聚合边界(Aggregate Boundary):定义了事务一致性的范围,一个事务只修改一个聚合。
- 内部对象:聚合内的实体和值对象只能通过聚合根访问,不能持有外部引用。
GoBFF 聚合设计原则:
- 聚合根使用
interface定义公共方法,隐藏内部实现。 - 聚合内对象通过
private字段或internal包控制访问。 - 跨聚合通信使用领域事件,避免直接引用。
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 网关和数据存储。
2.6 六大构造块关系总结
这六大构造块共同构成了 DDD 的建模语言。在 GoBFF 中,它们被映射为以下代码结构:
| DDD 构造块 | Go 代码映射 | BFF 中的角色 |
|---|---|---|
| 实体 | struct + ID 字段 |
业务核心对象,如订单、用户 |
| 值对象 | struct + Equals() |
金额、地址、时间范围 |
| 聚合根 | struct + 公共方法 |
事务边界入口 |
| 领域服务 | struct + 方法 |
跨聚合业务编排 |
| 领域事件 | struct + 事件总线 |
异步解耦通信 |
| 限界上下文 | module / package |
微服务边界 |
核心原则:限界上下文是最大的边界,聚合是事务边界,实体和值对象是模型的最小单元。领域服务协调跨聚合操作,领域事件实现跨上下文通信。这六大构造块在 GoBFF 中直接决定了代码的组织方式和发布回滚的粒度。