Aggregate roots are responsible for maintaining the integrity of a cluster of objects, the aggregate.
Their primary responsibility is deciding if a command can be executed on the aggregate or not and record that decission.
Defining an Aggregate Root
To define an Aggregate Root, create a class which inherits from EventSourced. Which is part of the MessageHandler.EventSourcing nuget package.
PM> Install-Package MessageHandler.EventSourcing
EventSourced can be found in the MessageHandler.EventSourcing.DomainModel namespace.
using MessageHandler.EventSourcing.DomainModel
And requires an id to be passed in. This id field is used to retrieve the correct stream from the event store.
public class OrderBooking : EventSourced
{
public OrderBooking(string id) : base(id)
{
}
}
Naming Aggregate Roots
We advice to name aggregate roots after process steps and not regular entities.
For example: call your aggregate root OrderBooking, instead of Order.
As the booking step is the point in an e-commerce process where a PurchaseOrder (what the buyer asks) gets converted into a SalesOrder (what the seller promises to delivery).
Modeling your domain model along these axis will result in more maintainable code in the long run.
Where is the aggregate?
The entities PurchaseOrder and SalesOrder are part of the aggregate that is being protected by this root.
At least conceptually... they may or may not explicitly exist as classes, in the private model of the root class.
That said, their data must be embedded in the events, so that at any time in the future, when the root has a need for that data to validate the invariants of the aggregate, code can be added to do so.
Sourcing from events
In order to protect the invariants of the aggregate, the root usually needs access to data from prior decissions it took.
Therefore the root must be able to restore its internal model by reapplying persisted events.
To restore internal state, implement the IApply<TEvent> interface for every event type emitted from the instance before.
In the implementation of the Apply method, copy over any state that may be embedded on the event into internal fields.
Only copy information that is actually needed to validate the business rules required, and nothing more to reduce future refactoring effort.
public class OrderBooking : EventSourced,
IApply<BookingStarted>
{
private bool _allreadyStarted;
public OrderBooking(string id) : base(id)
{
}
public void Apply(BookingStarted msg)
{
_allreadyStarted = true;
}
}
Before you can invoke a command on the aggregate instance, you need to provide it its history first.
Manually sourcing events
This can be done manually, by passing in an IEnumerable<SourcedEvent> to the RestoreFrom method.
var history = new List<SourcedEvent>();
var booking = new OrderBooking();
booking.RestoreFrom(history);
Using the aggregate repository
Sourcing events can also be taken care of for you, using the Get operation on an instance of IEventSourcedRepository<TEventSourced>.
IEventSourcedRepository<OrderBooking> repository = runtime.CreateAggregateRepository<OrderBooking>();
var booking = await repository.Get(bookingId);
This Get operation will load the events from the underlying eventstore, using TEventSourced as a stream type and the passed in id as stream id.
All events will be deserialized, sorted, and passed into the RestoreFrom operation, of a new instance, prior to returning the instance.
The restored instance is now ready to handle new commands.
Implementing commands
Implementing commands is as straightforward as adding a new method to the class, e.g. PlacePurchaseOrder.
The method needs to do two things:
- Validate if the command can be executed.
- Record the decission by passing an event representing the decission to the Emit method on the base class.
public class OrderBooking : EventSourced,
IApply<BookingStarted>
{
private bool _allreadyStarted;
public OrderBooking(string id) : base(id)
{
}
public void PlacePurchaseOrder(PurchaseOrder purchaseOrder)
{
if(_allreadyStarted) return;
Emit(new BookingStarted(){
PurchaseOrder = purchaseOrder
});
}
public void Apply(BookingStarted msg)
{
_allreadyStarted = true;
}
}
Passing an event to the Emit method, will also call the corresponding Apply method, so that you can keep any internal state changes in a single place.
This way you can guarantee that the internal state of an instance is the same regardless if it got restored from the eventstore, or built up by invoking commands.
Persisting changes
Emitted events, are not yet persisted though, they exist in memory only.
The most common scenario is to leave this responsibility to the aggregate repository, the one used to obtain the instance, by calling Flush.
await repository.Flush());
Unittesting
While unittesting it might be desireable to manually commit the changes, by calling Commit on the root instance, instead.
[Fact]
public void GivenNewBookingProcess_WhenPlacingValidPurchaseOrder_ThenPurchaseOrderIsCarriedOnEvent()
{
// given
var history = new List<SourcedEvent>();
var booking = new OrderBooking(bookingId);
booking.RestoreFrom(history);
//when
var purchaseOrder = new PurchaseOrder();
booking.PlacePurchaseOrder(purchaseOrder);
//then
var pendingEvents = booking.Commit();
var bookingStarted = pendingEvents.FirstOrDefault(e => typeof(BookingStarted).IsAssignableFrom(e.GetType()));
Assert.NotNull(bookingStarted.PurchaseOrder);
}
This allows you to inspect the messages that would otherwise be stored.
Resolving repository from the IOC container
The EventSourcedRepository<OrderBooking> is also available for resolution from the IOC container, if the service collection was passed into the handler runtime configuration.
The EventSourcedRepository<OrderBooking> is registered with its interface IEventSourcedRepository<OrderBooking> and can be injected like this:
public class HomeController
{
public HomeController(IEventSourcedRepository<OrderBooking> repository)
{
}
}