Skip to main content
With our commands, events, and domain errors defined, we can now bind them together by implementing the Aggregate trait. The aggregate root maintains our application’s internal state. This state is rebuilt by replaying events, and is used to validate incoming commands.

1. Defining the Aggregate Root Struct

Create your aggregate root struct. It should contain fields representing the internal state (such as balance, owner, or id). The framework tracks the stream revision for you via LoadedAggregate:

2. Implementing the Aggregate Trait

Now, implement the Aggregate trait for the BankAccount struct. This binds all components together:

Important Rules of Aggregates

When implementing your own aggregates, always follow these rules:
  1. Deterministic apply: The apply method is run every time your aggregate state is reconstituted from history. It must be completely deterministic, have no side effects, and only mutate fields. Never perform validations or log actions inside apply.
  2. Stateless handle: The handle method only reads from the reconstituted state to validate business rules and emit events. It must never mutate any fields of the aggregate directly. State mutation is deferred entirely to apply.
If you are familiar with the Decider/Evolver pattern, this crate maps that vocabulary directly onto the Aggregate trait:
  1. Decider: Aggregate::handle is the decision function. It receives the current aggregate state plus a command, validates invariants, and returns the new events to persist or a domain error.
  2. Evolver: Aggregate::apply is the evolve function. It receives an event fact and mutates only in-memory aggregate state so replay can deterministically rebuild the current state.
The public API keeps the established handle and apply names for compatibility with existing users and generated code.