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,不能持有内部对象的引用。
常见误区:不要将聚合设计得过大。一个聚合应只包含必须保持强一致性的对象。跨聚合的最终一致性通过领域事件实现。
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
}
2.6 核心概念关系总结
这五个构造块共同构成了领域模型的骨架。实体和值对象是基本元素,聚合是一致性边界,领域服务处理跨对象逻辑,领域事件实现松耦合通信。
建模检查清单:
- 有唯一标识且会变化?→ 实体
- 只描述属性且不可变?→ 值对象
- 需要保证一组对象的一致性?→ 聚合
- 逻辑不属于任何实体/值对象?→ 领域服务
- 需要通知其他部分“发生了什么”?→ 领域事件