Event sourcing
Every write to the domain goes through an aggregate. The aggregate emits events. The events are the source of truth. This is how it works in Chronicler.
A write never updates a table directly. It goes through an aggregate. The aggregate checks the rules, then emits one or more events. The event stream is the source of truth. Read models are built from it.
The write path
Every domain write calls the aggregate's Try method. Try validates the command against the current state, then records the events.
// The command runs through the aggregate. On success it emits an event.
if err := account.Try(cmd, chronicler.WithActor(actor), chronicler.WithChronicle(id)); err != nil {
return err
}
Two rules hold here, and they are not optional.
- Never wrap the error from
Try. Return it as it is. The caller and the tests match on the exact error. - Always add the injectors.
WithActorrecords who acted.WithChroniclescopes the event to its world.
Do not wrap Try errors
Wrapping the error with fmt.Errorf breaks the callers and the tests that match on it. Return the error from Try directly.
Why events, not rows
The event stream keeps the full history of every change. That gives Chronicler three things a row-update model cannot.
- Replay. A read model can be rebuilt from the events at any time.
- Audit. Who changed what, and when, is a fact in the stream, not a guess.
- New views. A new read model reads the same events and projects its own shape.
What comes next
The events feed the read models. That is the Projections pattern.