2. 领域驱动设计(DDD)核心概念:实体、值对象、聚合、领域事件、限界上下文
领域驱动设计(Domain-Driven Design,DDD)由 Eric Evans 于 2003 年提出,是一套应对复杂业务建模的方法论。在微服务架构中,DDD 提供了将业务边界映射为服务边界的核心工具。本节将逐一解析 DDD 的五个核心构造块,并阐明它们在微服务通信中的角色。
2.1 实体(Entity)
定义:实体是具有唯一标识且在其生命周期内状态可变的领域对象。两个实体即使所有属性值相同,只要标识不同,就是不同的对象。
| 特征 | 说明 | Go 实现要点 |
|---|---|---|
| 唯一标识 | ID 不可变,用于区分对象 | 使用 string 或 uuid.UUID 类型 |
| 可变状态 | 属性可随时间变化 | 通过方法修改内部字段 |
| 生命周期 | 从创建到持久化再到删除 | 仓储(Repository)负责存取 |
| 相等性 | 基于 ID 而非属性值 | 实现 Equals() 方法比较 ID |
微服务通信视角:实体是跨服务传递业务状态的最小单元。在事件驱动架构中,实体 ID 是事件负载中的关键字段,用于接收方定位或重建实体。
// Go 实体示例:订单实体
type Order struct {
ID string `json:"id"`
CustomerID string `json:"customer_id"`
Items []OrderItem `json:"items"`
Status string `json:"status"`
Version int `json:"version"` // 乐观锁版本
}
func (o *Order) ChangeStatus(newStatus string) error {
// 状态校验逻辑
o.Status = newStatus
return nil
}
func (o *Order) Equals(other *Order) bool {
return o.ID == other.ID
}
2.2 值对象(Value Object)
定义:值对象通过属性值来定义自身,没有唯一标识,且一旦创建就不可变。两个值对象如果所有属性值相同,则视为相等。
| 特征 | 说明 | Go 实现要点 |
|---|---|---|
| 无标识 | 完全由属性值定义 | 不定义 ID 字段 |
| 不可变 | 创建后属性不可修改 | 字段导出为只读或使用 getter |
| 可替换 | 修改时创建新实例 | 返回新值对象而非修改原对象 |
| 相等性 | 基于所有属性值 | 实现 Equals() 逐字段比较 |
最佳实践:在 Go 中,值对象通常定义为结构体,字段首字母小写(包内私有),通过构造函数创建。事件负载中的金额、地址、时间范围等天然适合建模为值对象。
// Go 值对象示例:金额
type Money struct {
amount float64
currency string
}
func NewMoney(amount float64, currency string) Money {
return Money{amount: amount, currency: currency}
}
func (m Money) Amount() float64 { return m.amount }
func (m Money) Currency() string { return m.currency }
func (m Money) Add(other Money) (Money, error) {
if m.currency != other.currency {
return Money{}, errors.New("currency mismatch")
}
return NewMoney(m.amount+other.amount, m.currency), nil
}
func (m Money) Equals(other Money) bool {
return m.amount == other.amount && m.currency == other.currency
}
2.3 聚合(Aggregate)
定义:聚合是一组相关实体和值对象的集群,由一个聚合根(Aggregate Root)统一对外访问。聚合保证了内部数据的一致性和不变性,是事务一致性的边界。
聚合设计原则:
- 聚合要尽量小,只包含必须保持一致的实体和值对象
- 聚合根通过全局唯一 ID 引用,内部对象通过本地 ID 引用
- 跨聚合的修改必须使用领域事件实现最终一致性
- 一个事务只修改一个聚合
2.4 领域事件(Domain Event)
定义:领域事件是领域中发生的、对业务有意义的、过去时的事实记录。它是聚合之间、限界上下文之间异步通信的核心载体。
| 属性 | 说明 | 示例值 |
|---|---|---|
| EventID | 事件唯一标识 | evt_20250321_001 |
| AggregateID | 产生事件的聚合根 ID | order_98765 |
| EventType | 事件类型名称 | OrderSubmitted |
| OccurredOn | 事件发生时间戳 | 2025-03-21T10:30:00Z |
| Payload | 事件携带的业务数据 | JSON 序列化的值对象 |
// Go 领域事件示例
type OrderSubmittedEvent struct {
EventID string `json:"event_id"`
OrderID string `json:"order_id"`
CustomerID string `json:"customer_id"`
Total Money `json:"total"`
OccurredOn time.Time `json:"occurred_on"`
}
// 聚合根中发布事件
func (o *Order) Submit() ([]interface{}, error) {
if o.Status != "pending" {
return nil, errors.New("order must be pending to submit")
}
o.Status = "submitted"
event := OrderSubmittedEvent{
EventID: uuid.New().String(),
OrderID: o.ID,
CustomerID: o.CustomerID,
Total: o.calculateTotal(),
OccurredOn: time.Now().UTC(),
}
return []interface{}{event}, nil
}
2.5 限界上下文(Bounded Context)
定义:限界上下文是 DDD 中最关键的战术模式,它划定了模型应用的明确边界。每个限界上下文拥有自己的统一语言(Ubiquitous Language),内部模型对外部上下文不透明。
2.6 核心概念关系总结
| 概念 | 标识 | 可变性 | 事务边界 | 通信角色 |
|---|---|---|---|---|
| 实体 | 有唯一 ID | 可变 | 聚合内 | 事件发布者/订阅者 |
| 值对象 | 无 ID | 不可变 | 聚合内 | 事件负载数据 |
| 聚合 | 聚合根 ID | 聚合根可变 | 事务边界 | 事件源 |
| 领域事件 | 事件 ID | 不可变 | 跨聚合 | 异步消息 |
| 限界上下文 | 上下文名称 | 模型边界 | 服务边界 | 微服务单元 |
微服务架构映射:
- 一个限界上下文 = 一个微服务
- 聚合根 = 微服务中的核心业务对象
- 领域事件 = 微服务间异步通信的消息
- 值对象 = 事件负载中的数据传输对象(DTO)
- 实体 ID = 消息路由和关联的关键字段