Reference
Event
Reference for go-sequent events: the Event interface, event.Base envelope, the type Registry for JSON decoding, naming rules, and where upcasters fit.
Event
Package: event
An event is a past-tense fact with a type name and an envelope. Events are stored in the EventStore, delivered to projectors and workflows, and decoded through a registry.
The interface
type Event interface {
EventType() string
AggregateID() uuid.UUID
SequenceNumber() int64
CreatedAt() time.Time
}
The envelope is aggregate id, sequence number and creation time. When an aggregate calls Apply, those fields are filled in. The store keeps them in columns, so the JSON payload does not need to repeat them.
event.Base
Domain events embed event.Base and set a type name in their constructor:
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 implements the Envelope interface, which adds SetAggregateID, SetSequenceNumber and SetCreatedAt. The aggregate and the store call those setters. Your code should not.
Registry
The registry maps a stored type name to a factory. The store uses it to decode JSON into a concrete struct.
r.Register("bank.AccountOpened", func() event.Event { return &AccountOpened{} })
Register every event type before loading any stream. Registry also has New(name) and Has(name). Use the same *event.Registry for your store and your app. In the façade, that is app.Events:
app := sequent.New(sequent.Config{})
bank.RegisterEvents(app.Events)
store := memory.New(app.Events) // same registry
If the store is built with a different registry, load fails with an unknown-type error.
Naming and shape
- Use past tense:
MoneyDeposited, notDepositMoney. - Namespace type names:
bank.MoneyDeposited. The name is part of your stored data. - Keep events small and flat. One fact per event.
- Money amounts are
types.Money. Never usefloat64. - Record facts, not state snapshots. The aggregate derives state by applying events in order.
Renaming a type or reshaping its payload requires an upcaster, because old rows keep the old name. A semantic change should usually be a new event type, with the old events left alone.
Upcasters
Upcasters live in event/upcast and transform old payloads when events are loaded:
type Upcaster interface {
FromType() string
Upcast(payload json.RawMessage) (toType string, out json.RawMessage, err error)
}
Register them on the app:
app.RegisterUpcaster(upcast.FuncUpcaster{
From: "bank.MoneyDeposited.v0",
Fn: func(p json.RawMessage) (string, json.RawMessage, error) {
// rename amt → amount, return the new type name
},
})
An upcaster is a pure function of JSON in, JSON out. A chain follows renames for up to eight hops by default. Attach the chain to the store with SetUpcasters(app.Upcasters). The rules, a compatibility matrix and test expectations are on Event evolution.
Upcasters are for your own history. They are not a way to read a Ruby Sequent database; that is never supported.
Corrections
Events are immutable facts. To fix a mistake, append a compensating event, such as a reversal. Do not update or delete event rows. The store checks a hash chain on load, and a silent edit breaks it.
Related
- AggregateRoot: how
Applyfills the envelope - EventStore: persistence and the hash chain
- Event evolution
Docs track the go-sequent library. When APIs drift, the source wins.