3. 领域驱动设计入门:实体、值对象、聚合、领域事件核心概念
领域驱动设计(Domain-Driven Design,DDD)是一套应对复杂业务系统的建模方法论。在分布式事务场景中,DDD 帮助我们划定事务边界、识别一致性单元。本章聚焦 DDD 的四大核心构造块:实体、值对象、聚合 和 领域事件,并解释它们如何与 Go 语言实现及分布式事务画布对齐。
3.1 实体(Entity):拥有唯一标识的领域对象
实体是具有唯一标识且可变的领域对象。两个实体即使属性完全相同,只要标识不同,就是不同的对象。在分布式系统中,实体的 ID 通常由 UUID、雪花算法或数据库序列生成。
关键特征:
- 拥有全局或局部唯一的标识符(ID)。
- 生命周期内状态可以变化,但标识不变。
- 通过 ID 相等性判断,而非属性值。
Go 实现示例:
// Order 是订单实体,使用 UUID 作为唯一标识
type Order struct {
ID string // 唯一标识,不可变
CustomerID string
Items []OrderItem
Status OrderStatus
CreatedAt time.Time
}
// SameIdentityAs 比较两个实体的标识是否相同
func (o *Order) SameIdentityAs(other *Order) bool {
return o.ID == other.ID
}
3.2 值对象(Value Object):描述性、不可变、无标识
值对象通过属性值来定义自身,没有概念上的唯一标识。两个值对象如果所有属性都相同,则视为同一个对象。值对象通常是不可变的,修改时返回新实例。
| 对比维度 | 实体(Entity) | 值对象(Value Object) |
|---|---|---|
| 标识 | 有唯一标识 | 无标识,由属性定义 |
| 可变性 | 可变 | 不可变 |
| 相等性 | ID 相等 | 所有属性相等 |
| 生命周期 | 独立生命周期 | 依附于实体 |
| 分布式事务影响 | 事务边界核心 | 随实体一起持久化 |
Go 实现示例:
// Money 是值对象,表示金额
type Money struct {
amount decimal.Decimal
currency string
}
// NewMoney 工厂方法,返回不可变实例
func NewMoney(amount decimal.Decimal, currency string) Money {
return Money{amount: amount, currency: currency}
}
// Add 返回新的 Money 实例,不修改原对象
func (m Money) Add(other Money) (Money, error) {
if m.currency != other.currency {
return Money{}, errors.New("currency mismatch")
}
return NewMoney(m.amount.Add(other.amount), m.currency), nil
}
// Equals 比较两个值对象是否相等
func (m Money) Equals(other Money) bool {
return m.amount.Equal(other.amount) && m.currency == other.currency
}
3.3 聚合(Aggregate):事务一致性的边界
聚合是 DDD 中最重要的构造块,它定义了事务一致性的边界。一个聚合是一组相关对象的集群,外部只能通过聚合根(Aggregate Root)来访问内部对象。在分布式事务画布中,一个聚合对应一个本地事务边界。
聚合设计三原则:
- 聚合根保护不变条件:聚合根是唯一对外接口,内部对象不能直接暴露。
- 跨聚合最终一致性:不同聚合之间通过领域事件实现最终一致,而非强事务。
- 一个事务只修改一个聚合:这是分布式事务拆分的基本单元。
Go 实现示例:
// OrderAggregate 是订单聚合根
type OrderAggregate struct {
order *Order // 聚合根实体
items []OrderItem // 聚合内部实体
history []OrderEvent // 领域事件列表
}
// NewOrderAggregate 创建聚合,确保不变条件
func NewOrderAggregate(customerID string) (*OrderAggregate, error) {
if customerID == "" {
return nil, errors.New("customer ID is required")
}
agg := &OrderAggregate{
order: &Order{
ID: uuid.New().String(),
CustomerID: customerID,
Status: OrderStatusPending,
CreatedAt: time.Now(),
},
}
// 记录领域事件
agg.addEvent(OrderCreated{OrderID: agg.order.ID})
return agg, nil
}
// AddItem 通过聚合根修改内部状态
func (agg *OrderAggregate) AddItem(productID string, quantity int, price Money) error {
if agg.order.Status != OrderStatusPending {
return errors.New("can only add items to pending order")
}
item := OrderItem{
ProductID: productID,
Quantity: quantity,
Price: price,
}
agg.items = append(agg.items, item)
agg.addEvent(ItemAddedToOrder{OrderID: agg.order.ID, ProductID: productID})
return nil
}
// addEvent 内部方法,记录领域事件
func (agg *OrderAggregate) addEvent(event OrderEvent) {
agg.history = append(agg.history, event)
}
3.4 领域事件(Domain Event):跨聚合的通信契约
领域事件是已经发生的事实,用于聚合之间的异步通信。在分布式事务画布中,领域事件是最终一致性的核心载体。当一个聚合完成状态变更后,发布领域事件,其他聚合订阅并做出响应。
领域事件设计要点:
- 事件名称使用过去时(如 OrderCreated、PaymentCompleted)。
- 事件包含足够的数据供订阅者处理,但不包含敏感信息。
- 事件是不可变的,发布后不可修改。
- 事件通常携带事件 ID 和 时间戳,用于去重和追踪。
Go 实现示例:
// OrderCreated 领域事件:订单已创建
type OrderCreated struct {
EventID string `json:"event_id"`
OrderID string `json:"order_id"`
CustomerID string `json:"customer_id"`
Total Money `json:"total"`
OccurredAt time.Time `json:"occurred_at"`
}
// NewOrderCreated 创建领域事件实例
func NewOrderCreated(orderID, customerID string, total Money) OrderCreated {
return OrderCreated{
EventID: uuid.New().String(),
OrderID: orderID,
CustomerID: customerID,
Total: total,
OccurredAt: time.Now(),
}
}
// EventBus 事件总线接口,用于发布和订阅领域事件
type EventBus interface {
Publish(event interface{}) error
Subscribe(eventType string, handler func(event interface{})) error
}
// 库存聚合订阅订单创建事件
func SubscribeOrderCreated(bus EventBus, inventoryService *InventoryService) {
bus.Subscribe("OrderCreated", func(event interface{}) {
e := event.(OrderCreated)
// 异步扣减库存,实现最终一致性
inventoryService.ReserveStock(e.OrderID, e.CustomerID)
})
}
3.5 四大构造块在分布式事务画布中的角色
| 构造块 | 分布式事务角色 | 事务边界 | 一致性模型 |
|---|---|---|---|
| 实体 | 事务参与者,状态变更的主体 | 聚合内部 | 强一致性 |
| 值对象 | 事务数据载体,不可变快照 | 随实体持久化 | 强一致性 |
| 聚合 | 事务边界单元,ACID 保证 | 聚合根 | 强一致性 |
| 领域事件 | 跨聚合通信契约,异步协调 | 聚合之间 | 最终一致性 |
实战要点:
- 在 Go 中,聚合根通常是一个结构体,内部持有实体和值对象的引用。
- 领域事件使用接口或结构体定义,通过事件总线(如 RabbitMQ、Kafka 或内存通道)发布。
- 分布式事务画布中,每个聚合对应一个本地事务,跨聚合操作通过 Saga 或事件驱动实现。
- 值对象在 Go 中推荐使用不可变设计,所有修改方法返回新实例。