-
Notifications
You must be signed in to change notification settings - Fork 60
Core
The core library (AggregateSource) contains interfaces so both testing and event storage can communicate with your aggregates. Think of aggregates as lines you've drawn around one or more entities. The line is a boundary and carries a meaning: consistent on the inside, eventually consistent on the outside. There is usually only one entity within the aggregate that calling code communicates directly with. It's called the aggregate root entity a.k.a. the aggregate root. Next to the root entity you may have other entities and value objects within the boundary of an aggregate.
The following picture should clarify how an event is born, as a side effect of invoking a method on the aggregate root.
For a new entity this flow is a bit more complicated.
A similar flow exists for an existing entity.
An interface that provides the contract for getting the tracked changes of the aggregate. Typically, commands will induce one or more events. It's these events - applied on the aggregate root entity - that will end up as a tracked change.
An interface that provides the contract for performing aggregate initialization. Typically this involves replaying an enumeration of events onto the aggregate root entity which reconstructs the aggregate's state, including the root, any value objects and/or entities, however deep you may choose them to be.
An interface that aggregates IAggregateInitializer
and IAggregateChangeTracker
interfaces. This is the interface the library interacts the most with.
Eventhough these interfaces are pretty strict, there are many ways you could implement them. Reimplementing them on each and every aggregate will probably introduce too much boilerplate and distract the reader of the aggregate's code from what the actual domain behavior is. Most people tend to create a base class that contains the code that is common to all eventsourced aggregates. Nonetheless, over the years, a lot of different implementation styles have emerged. This is where the AggregateSource.Content.* nuget packages come in. I invite you to look at them and pick the one that suits you best. Because this is one of the primary points of integration between your code and this library, I urge you to consider the trade-offs of each approach.
Requires you to register a callback handler for each event that changes the aggregate's state. This can also be seen as an advantage, you only have to register callback handlers for events that actually change the internal state of the aggregate. Those that don't need to change the internal state do not require a callback handler. Another advantage is that you can use an Action<TEvent>
instead of a dedicated method as callback handler which slightly reduces the amount of code to read and type.
The state of your aggregate is stored in an object under your control, yet separate from the aggregate itself. Events are applied to this separate state object, not the class you code the behavior in. The advantage is that it brings clarity, you only see behavior. Requires you to register a callback handler for each event that changes the aggregate's state. This can also be seen as an advantage, you only have to register callback handlers for events that actually change the internal state of the aggregate. Those that don't need to change the internal state do not require a callback handler. Another advantage is that you can use an Action<TEvent>
instead of a dedicated method as callback handler which slightly reduces the amount of code to read and type. For convenience an EntityState base class is provided, furthermore reducing the amount of boilerplate.
Next to an AggregateRootEntity class, each of these packages also contains an Entity implementation that follows the same design, as a convenience. These packages are content-based, meaning they will add code to the project you install them in.
It's important to realize these packages are not the only implementation one could come up with. In the future I hope to add convention based ones, where the state handlers are discovered at runtime.