Using Event Sourcing? Prefer the Decider over AggregateRoot
Jeremy D. Miller strongly recommends that Event Sourcing developers model domain logic with the Decider pattern instead of the older AggregateRoot pattern. He explains why pure decide functions in Marten and Wolverine handlers are easier to reason about, test and debug. The post is a useful reference for anyone choosing a design for event-sourced .NET systems.
Key points
- Keep decision logic in a pure function that takes current state and a command and returns new events, with no infrastructure calls.
- With Marten and Wolverine, the infrastructure mediates between the command handler and the domain function.
- Decider functions can be unit tested directly, without a database or mocks.
- Do not mutate projected state objects in application code; Marten's optimizations depend on that assumption.
- Separate the evolve logic that builds state from the decide logic to make projection changes easier.
- For new Event Sourcing work, treat the Decider as the default and AggregateRoot as legacy.
While reviewing sample code in an upcoming CQRS and Event Sourcing book, the author noticed the examples were built on the older AggregateRoot pattern. Jeremy D. Miller argues that teams doing Event Sourcing should express domain logic with the Decider pattern and drop AggregateRoot. In a Decider, a pure function takes the current state and an incoming command and returns the new events to append, while all infrastructure sits outside it.
His reasons are that the Decider makes the link between commands and emitted events clearer, is easier to unit test, and cuts down on the troubleshooting problems he has seen with aggregates. He also notes that Marten's optimizations assume projected state is not mutated by application code, and that separating the evolve logic from decide logic eases projection changes. The post also covers the older AggregateRoot sample and how Wolverine handlers support the approach.
Real-world use
- An order service that records state transitions as events, validating order commands with pure decide functions.
- A Marten-based event-sourced line-of-business app that wires command handlers through Wolverine.
- Migrating a legacy AggregateRoot codebase to Decider functions one aggregate at a time.
Why it matters
Writing Event Sourcing logic as Decider functions makes the command-to-event relationship explicit and simplifies unit tests. For teams with existing AggregateRoot code, the post gives a clear recommendation for new work.