-
Notifications
You must be signed in to change notification settings - Fork 39
Emit Apply Pattern
When you're working with event sourcing and a rich domain model centered around aggregate roots, you and your code need to satisfy the following conditions:
- Performing actions in the domain results in emitting one or more events
- Those events are collected and are the only things that are actually persisted
and it follows from this that
- Your domain needs to be able to rebuild its state completely and solely from events
because events are the only thing that are actually persisted.
At the moment, we know of no better way than to use the Emit/Apply pattern. With this pattern, we ensure that the aforementioned conditions hold, while still providing a tolerable experience for the programmer.
This is how it's done:
- Aggregate root provides a method that can be called
- Aggregate root does NOT change itself inside this method - instead it emits one or more events by calling
Emit
on itself - An emitted event is immediately dispatched to the aggregate root's appropriate
Apply
method - Aggregate root mutates itself to reflect the state that follows as a natural consequence of the applied event
- Emitted events are accumulated during the "unit of work" and are saved to the event store if all went well
- Events must never be emitted as a consequence of applying another event
This way, a framework is free to create a fresh instance of an aggregate root and apply any number of the previously emitted events in order to make the aggregate root reflect any point in time.
With Cirqus, you do it like this - create an aggregate root by creating a class that is derived off of AggregateRoot
, like this:
public class Beetroot : AggregateRoot { }
Now, when I have a Beetroot
, I want to be able to squash it - so I add a method that squashes this particular beetroot (and the squashing degree can be varied by supplying a value for the howMuch
parameter):
public class Beetroot : AggregateRoot {
public void Squash(decimal howMuch) { }
}
And then the rules state that we must only mutate ourselves as a consequence of an event, so we emit an event:
public class Beetroot : AggregateRoot {
public void Squash(decimal howMuch) {
Emit(new BeetrootSquashed { SquashingDegree = howMuch });
}
}
and we must declare an appropriate domain event in order to be able to do this:
public class BeetrootSquashed : DomainEvent<Beetroot> {
public decimal SquashingDegree { get; set; }
}
If you run the program we have so far with Cirqus, you'll get an error that tells you that you cannot emit events of a particular type from an aggregate root unless you state your intent by implementing an appropriate IEmit<>
interface - in this case, we must do it like this:
public class Beetroot : AggregateRoot, IEmit<BeetrootSquashed> {
public void Squash(decimal howMuch) {
Emit(new BeetrootSquashed { SquashingDegree = howMuch });
}
public void Apply(BeetrootSquashed e) {
// in here, we can mutate ourselves!
}
}
So, let's imagine a world where beetroots start out with a structural stamina of 1
and then, whenever they're squashed, their stamina is reduced by that number - that world could be implemented like this:
public class Beetroot : AggregateRoot, IEmit<BeetrootSquashed> {
decimal _structuralStamina = 1;
public void Squash(decimal howMuch) {
Emit(new BeetrootSquashed { SquashingDegree = howMuch });
}
public void Apply(BeetrootSquashed e) {
_structuralStamina -= e.SquashingDegree;
}
}
See how the aggregate root is free to keep a truly private
variable private as long as it's adhering to the Emit/Apply Pattern? Last thing is that beetroots (in our world) must tell the world when they're completely crushed, which they must do by emitting an appropriate event.
Since we must never emit an event as a consequence of another event, we take advantage of the fact that emitted events are immediately applied, which means that the private _structuralStamina
has been changed after the first Emit
- therefore, we extend the beetroot to do this:
public class Beetroot : AggregateRoot,
IEmit<BeetrootSquashed>,
IEmit<BeetrootCompletelyCrushed>
{
decimal _structuralStamina = 1;
public void Squash(decimal howMuch) {
Emit(new BeetrootSquashed { SquashingDegree = howMuch });
// if we haven't been completely crushed, just return
if (_structuralStamina > 0) return;
Emit(new BeetrootCompletelyCrushed());
}
public void Apply(BeetrootSquashed e) {
_structuralStamina -= e.SquashingDegree;
}
public void Apply(BeetrootCompletelyCrushed e) {
}
}
so that an appropriate BeetrootCompletelyCrushed
is emitted when the stamina is no longer greater than zero.
The Emit/Apply Pattern enables rich domain models with complicated logic to be built from events and to reflect all work in the form of events, while still providing a useful state snapshot that can be utilized in computations and logic.
Combined with a framework that takes care of event application and all the other stuff around the aggregate root, this provides a powerful model for creating event-driven models.