At the fundamental level, we only need one function for each aggregate root to handle its behaviours. The function will calculate a sequence of events in response to a command. Command is an intention to trigger a behaviour.
Command in, events out
Let's call this function Execute since its job is to execute the command.
Note: I chose Execute as the name for the function following one of the naming strategies for aggregate methods found in "Learning Domain-Driven Design" by Vladik Khononov.
History is not forgotten
As we execute commands for any given aggregate, events will be appended to a journal (event store). Past events will be needed to handle future commands for that aggregate.
For example, a game cannot be played before it is started or after it is lost. Or, once an order has shipped it can no longer be amended.
We will look into the history to make decisions.
Therefore, the Execute function will also need the past events. That list will be empty when the first command for a given aggregate is executed.
Note: We use Arrow for typed errors, specifically the Either and NonEmptyList types. However, any type-safe error-handling library would work. Result4k is a nice alternative to Arrow.
The matter of state
As we defined it so far, the Execute function is the minimum we need to implement an event-sourced domain model.
Here's an example of a further unidentified game pending the implementation:
Let's assume we don't want to allow to make guesses once the game is won. The game is considered won when the GameWon event is published. In such a case, any MakeGuess command should be rejected with an error.
As we suggested before, we will need to look at the history of events to make decisions. Having the list of events passed to the Execute function we can scan it for facts:
events.filterIsInstance<GameWon>().isNotEmpty()
Furthermore, we can name any concept by defining an extension function for it:
Remember the Game here is actually a List<GameEvent>.
Thanks to extension functions and our previously defined type alias for the Game (as a list of events), it becomes very natural to use:
if(game.isFinished()){returnGameAlreadyFinished(gameId).left()}// Note: `.left()` wraps the result in an `Either.Left` // that represents the error branch.
Just as if we had the Game class defined.
It is just a list of events though.
It will work rather well for short streams of events. However, scanning long streams might get expensive, especially if we make decisions based on a lot of factors. For such scenarios, we can introduce an optimisation in the form of a computed state (projection).
The entity's state can be derived from its history of events.
The difference between using a computed state and scanning events history is that when we reconstruct the state object every event is applied only once to update the state. Then, we can access it however many times we need. In the case of event scanning, the scan is performed every time we need to access a bit of information.
Note: We can go a long way without having to introduce a computed state if we treat the history of events as the state and scan it for information as shown above.
I often use this technique as an intermediate step. It helps me to learn what the properties of the state object should look like. It sometimes turns out to be a different view than I thought of before the implementation.
Execute with state
Let's redefine the Execute function one last time to account for the possibility of the state being any kind of type (not just an events list).
Final form of the Execute function
Here's the final revision of the Execute function type alias:
The state is made nullable as it won't exist when the first command is executed.
Alternatively, we could model the initial state as a special type. The choice depends on the use case. Sometimes it's natural to have a dedicated initial state object; while in other cases it makes more sense for the initial state to be null (as no natural initial state exists). As we know, in Kotlin we don't need to be afraid of nulls.
State reconstruction
We will need a way to reconstruct the state with events we got from the event store so that we can pass it to the Execute function.
Instead of scanning through the history of events to access every bit of information, we will instead build the state by applying events to it one by one.
Current state is a left fold of previous behaviours.
Greg Young in many of his talks
To calculate the state, we will start with null (or other initial state), and fold events with an applyEvent function.
valstate=events.fold(null,::applyEvent)
Left fold of previous events
The applyEvent function, or simply Apply, should take the state, an event, and return a new state.
Apply calculates a new state for the given state and event
In other words, it will calculate the new state based on the previous state and an event.
Here's the type alias for the Apply function:
typealiasApply<STATE,EVENT>=(STATE,EVENT)->STATE
Apply function type alias
Here’s how it could look like for a further unidentified game:
Folding events over the state means that the Apply function is called on the initial state and the first event to calculate a new state, which is in turn passed to the next invocation of Apply with the next event. This process is repeated until we run out of events and return the final version of the state.
Fold of events illustrated
All together now
The command will usually come from a user in one way or another (sometimes from other systems, in reaction to events or other commands etc).
Events will be loaded from an event store.
These two are not compatible parameters for the Execute function if we decide to use a computed state.
We will need to convert:
To:
The conversion of one type to another isn’t too complicated. We can implement a creational function that takes one version of Execute and returns the desired one:
Apart from Execute we will also need to pass Apply to fold events and build the state.
Additionally, it’s just too easy to support any initial state, not just nulls. That’s the job for the initialState function.
Changing the type of `Execute` to match received parameters
Inspirations
Before I started a project with this approach I did a lot of reading on functional programming techniques for event sourcing. One notable read I remember is Functional event sourcing decider by Jeremie Chassaing. An attentive reader will notice that what I described here comes very close to Jeremie's decider pattern.
I didn't set out to implement the decider pattern, but it kind of happened anyway. Kind of. Not really. Maybe?
Depends on how far you'll allow us to diverge from the pattern.
The decider pattern is made of the following parts:
Explore a sea of insights with this enlightening post, highly esteemed within the nurturing DEV Community. Coders of all stripes are invited to participate and contribute to our shared knowledge.
Expressing gratitude with a simple "thank you" can make a big impact. Leave your thanks in the comments!
On DEV, exchanging ideas smooths our way and strengthens our community bonds. Found this useful? A quick note of thanks to the author can mean a lot.
Top comments (1)
github.com/fraktalio/fmodel