参考
Event
Event 接口、event.Base 信封、JSON 解码用的类型 Registry、命名约定,以及 upcaster 的位置。
Event
包:event
Event 是已经发生的事实:有类型名和信封。事件写入 EventStore,交给 Projector 与 Workflow,再通过 registry 解码。
接口
type Event interface {
EventType() string
AggregateID() uuid.UUID
SequenceNumber() int64
CreatedAt() time.Time
}
信封包括 aggregate id、序号和创建时间。聚合调用 Apply 时会填好这些字段。store 把它们放在独立列里,JSON 载荷不必再重复一份。
event.Base
领域事件嵌入 event.Base,在构造函数里设好类型名:
type MoneyDeposited struct {
event.Base
Amount types.Money `json:"amount"`
}
func NewMoneyDeposited(amount types.Money) *MoneyDeposited {
e := &MoneyDeposited{Amount: amount}
e.SetEventType("bank.MoneyDeposited")
return e
}
func (e *MoneyDeposited) EventType() string { return "bank.MoneyDeposited" }
Base 实现了 Envelope,另外提供 SetAggregateID、SetSequenceNumber、SetCreatedAt。这些 setter 由聚合和 store 调用,业务代码不要自己调。
Registry
Registry 把已存储的类型名映射到工厂函数,store 靠它把 JSON 解成具体结构体。
r.Register("bank.AccountOpened", func() event.Event { return &AccountOpened{} })
加载任何事件流之前,必须注册所有事件类型。Registry 还提供 New(name) 和 Has(name)。store 与应用要共用同一个 *event.Registry;在门面里就是 app.Events:
app := sequent.New(sequent.Config{})
bank.RegisterEvents(app.Events)
store := memory.New(app.Events) // 同一个 registry
store 若用了另一个 registry,加载时会报未知类型。
命名与形状
- 用过去时:
MoneyDeposited,不要写成DepositMoney。 - 类型名带命名空间:
bank.MoneyDeposited。这个名字会跟着数据一起存。 - 事件要小而扁,一个事件一件事。
- 金额用
types.Money,绝不用float64。 - 记的是事实,不是状态快照。聚合靠按序应用事件推出当前状态。
改类型名或改载荷结构需要 upcaster——旧行还挂着旧名字。语义变了,通常应新增事件类型,旧事件原样保留。
Upcaster
Upcaster 在 event/upcast,加载事件时把旧载荷转成新形状:
type Upcaster interface {
FromType() string
Upcast(payload json.RawMessage) (toType string, out json.RawMessage, err error)
}
在应用上注册:
app.RegisterUpcaster(upcast.FuncUpcaster{
From: "bank.MoneyDeposited.v0",
Fn: func(p json.RawMessage) (string, json.RawMessage, error) {
// amt 改名为 amount,并返回新类型名
},
})
Upcaster 是纯函数:JSON 进,JSON 出。链默认最多跟随八次改名。用 SetUpcasters(app.Upcasters) 把链挂到 store 上。规则、兼容矩阵与测试要求见 事件演进。
Upcaster 只处理你自己的历史数据,不是用来读 Ruby Sequent 库的;那条路永远不支持。
修正错误
事件是不可变事实。要纠错,就追加补偿事件(例如冲正),不要改或删事件行。store 加载时会校验 hash chain(按聚合串联事件哈希,用来发现篡改),静默改库会把链弄断。
相关
- AggregateRoot:
Apply如何填信封 - EventStore:持久化与 hash chain
- 事件演进
文档随 go-sequent 源码更新;若与 API 不一致,以源码为准。