How do We Write Factories for Event Sourced Aggregates?
Take the following use case:
- I have an Employee aggregate
- I want to create a new Employee
- I wrote
Employee.Create(...)
…but Aggregates don’t create themselves (Don’t Create Aggregate Roots), especially when it’s a clear use case where someone like an Admin is responsible for creating new Employees.
We’re suppose to create a factory for situations like this. This way, the factory can ensure invariants are enforced on the creation of the Aggregate, just like the Aggregate enforces invariants on any other state changes.
But, when I created a static factory Employee.Create(...)
and intend for Employee to be an Event Sourced Aggregate, when/where/how do I send my EmployeeCreated
event so that it gets saved to the Event Store?
Review
Recall the basic structure of an Event Sourced Aggregate Root. Instead of the Aggregate itself, in a valid state, being saved directly to the database using some ORM framework - we collect events that are applied to the Aggregate and save them to the Event Store, replaying them to arrive at the current state of the Aggregate later down the road.
A go-to example is Greg Young’s SimpleCQRS project:
public abstract class AggregateRoot
{
private readonly List<Event> _changes = new List<Event>();
public abstract Guid Id { get; }
public int Version { get; internal set; }
public IEnumerable<Event> GetUncommittedChanges()
{
return _changes;
}
public void MarkChangesAsCommitted()
{
_changes.Clear();
}
public void LoadsFromHistory(IEnumerable<Event> history)
{
foreach (var e in history) ApplyChange(e, false);
}
protected void ApplyChange(Event @event)
{
ApplyChange(@event, true);
}
// push atomic aggregate changes to local history for further processing (EventStore.SaveEvents)
private void ApplyChange(Event @event, bool isNew)
{
this.AsDynamic().Apply(@event);
if(isNew) _changes.Add(@event);
}
}
When we inherit this base class, and use the ApplyChange
method to represent a state change to the Aggregate.
public class InventoryItem : AggregateRoot
{
private bool _activated;
private void Apply(InventoryItemDeactivated e)
{
_activated = false;
}
public void Deactivate()
{
if(!_activated) throw new InvalidOperationException("already deactivated");
ApplyChange(new InventoryItemDeactivated(_id));
}
}
So in an already constructed Aggregate, we’re calling ApplyChange(Event @event, bool isNew)
, which calls the appropriate Apply(Event e)
method based on the Event
type to change the state of the Aggregate, which then tracks the event(s) and eventually saves them to the Event Store for later replay.
Instance factory
Given the above AggregateRoot
base class, it’s still unclear how we can use a static factory on the Aggregate instance, or on another Aggregate (per a real business case) to create a new Employee and also persist the necessary EmployeeCreated
event to the Event Store when we save the _changes
to the store.
In state change scenarios, we simply Create -> ApplyChange -> Apply
. But we can’t call ApplyChange
from a static method in a class that inherited AggregateRoot
without instantiating it first. If we only call Apply
, we can change the state of the Aggregate, but this won’t add any changes to the _changes
property and therefore won’t get saved to the Event Store.
With a static factory like Employee.Create()
, we don’t have access to ApplyChanges
method on the AggregateRoot
base class - so we’re stuck with instantiating an Employee
just to call the factory. We don’t want to be doing this for the sake of persisting the EmployeeCreated
event to the event store:
var employee = new Employee().Create();
Do we really Create the Employee?
One larger issue with this example is that we’ve created a Create
method to represent creating a new Employee. Is the Employee actually getting “Created?” More likely, the Employee is getting Hired
or Registered
in the Ubiquitous Language. Let’s make sure we’re not letting the implementation leak into the Domain as a first step.
So we change it to Employee.Hire
, how can we make it so that we can use this method in a Command Handler that will hook into the Event Sourcing infrastructure set up in the AggregateRoot
base class?
It might seem obvious to some, but I surely can’t blame anyone for getting stuck on things that should be simple :)
The pattern I use is to put a static factory on the Aggregate that calls ApplyChanges()
and then returns the Aggregate.
For example:
public class Employee : AggregateRoot {
...
public static Employee Hire() {
var employee = new Employee();
employee.ApplyChanges(new EmployeeCreated());
return employee;
}
...
}
Here’s another example from First Pop Coffee, the sample project from my latest book (P.S. stay tuned for some important updates regarding some major work I’ve been doing to continue the chronicle of First Pop Coffee Co.):
public class RoastDay : AggregateRootEntity
{
public RoastDayId RoastDayId { get; private set; }
public LocalDate RoastDate { get; private set; }
private RoastDay()
{
Register<PlannedNewRoastDay>(@event =>
{
RoastDayId = new RoastDayId(@event.Id);
RoastDate = @event.RoastDate;
});
}
public static RoastDay PlanNewForDate(string id, string roastDate)
{
if (roastDate == null) throw new ArgumentNullException(nameof(roastDate), "A roast date must be provided");
var roastDateAsLocalTime = LocalDatePattern
.CreateWithCurrentCulture("yyyy-MM-dd")
.Parse(roastDate)
.Value;
var roastDay = new RoastDay();
roastDay.ApplyChange(new PlannedNewRoastDay(Guid.Parse(id), roastDateAsLocalTime));
return roastDay;
}
}
Note this code uses AggregateSource by Yves Reynhout, so it is a bit different from SimpleCQRS’s AggregateRoot
base class. Much more on that in later posts.
Here are some specs that demonstrate protecting the invariants when constructing the RoastDay
Aggregate:
public class When_planning_a_new_roast_day
{
[Fact]
public void Should_accept_valid_date()
{
var roastDayId = Guid.NewGuid();
new ConstructorScenarioFor<RoastDay.RoastDay>(() =>
RoastDay.RoastDay.PlanNewForDate(roastDayId, "2017-11-22"))
.Then(new PlannedNewRoastDay(roastDayId, new LocalDate(2017, 11, 22))).Assert();
}
[Fact]
public void Should_throw_if_invalid_date()
{
var roastDayId = Guid.NewGuid();
new ConstructorScenarioFor<RoastDay.RoastDay>(() =>
RoastDay.RoastDay.PlanNewForDate(roastDayId, "123456"))
.Throws(new NodaTime.Text.UnparsableValueException());
}
}
Summary
This was a quick discussion of a pattern used to hook up Aggregate construction to the rest of our Event Sourcing infrastructure for Aggregates that we want to be Event Sourced.
It’s easy to get into a situation where you’re wondering “why isn’t this as simple as it should be?” and a simple solution is presented that you think “why didn’t I think of this?” I think that’s ok, and there are probably more ways to solve this problem. Hopefully it helps you keep moving if you run into any confusion around this scenario in the future.
As always, please hit the red button below to sign up for content about the finer nuances of Domain-Driven Design, CQRS, and Event Sourcing - as well as various other architecture topics that are sure to help you out.