We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
1. Building Your First Event-Sourced Application
For this book I wanted to start off with my own definition and then see how far off I am. I think of event sourcing as a log of events that you can then run through to get to the current state of the source. In this way any change will be added and then we can not have to hold the entire state and we can better hold all the events and then deal with the upkeep later.
Martin Fowler describes it as:
The fundamental idea of Event Sourcing is that of ensuring every change to the
state of an application is captured in an event object, and that these event
objects are themselves stored in the sequence they were applied for the same
lifetime as the application state itself.
You want to be able to derive the current state from the past events. This will require you to store all the events in the right order and will always be able to get to the right state.
Saying “Hello, Procedural World”
There will be some definition that will be used throughout the book, procedural and imperative or you might see stateful or state replication. These refer to non-event sourcing and event sourcing. These will not always perfectly work for the specific version we will work with they might be the best.
Let’s start off by creating a simple calculator and running some iex sessions. I created calculator/calculator.ex
defmodule Calculator do
def add(x, y) do
x + y
end
def mul(x, y) do
x * y
end
def div(x, y) do
x / y
end
def sub(x, y) do
x - y
end
end
iex(1)> c("calculator/calculator.ex")
[Calculator]
iex(2)> Calculator.add(2,2)
4
iex(3)> 8 |> Calculator.add(4) |> Calculator.mul(3)
36
You can see that we can chain events but the state between the “|” is lost. A normal calculator will hold the state. Hitting 2 + 2 = will give you 4 then + 6 will give you 10.
There is an other way of looking at it and that is a stack:
| before | after |
|---|---|
| add | 4 |
| 2 | — |
| 2 | — |
Now we can start to use a event state that will store the current value.
Building a Stateful and Imperative Calculator
An imperative is an other word for command. Let’s refactor so that we hold a value.
defmodule EventSourcedCalculator.V1 do
def handle_command(%{value: val}, %{cmd: :add, value: v}) do
%{value: val + v}
end
def handle_command(%{value: val}, %{cmd: :sub, value: v}) do
%{value: val - v}
end
def handle_command(%{value: val}, %{cmd: :mul, value: v}) do
%{value: val * v}
end
def handle_command(%{value: val}, %{cmd: :div, value: v}) do
%{value: val / v}
end
end
This will allow some functionality like.
iex(4)> c("calculator/event_sourced_calculator.ex")
[EventSourcedCalculator.V1]
iex(5)> EventSourcedCalculator.V1.handle_command(%{value: 22}, %{cmd: :add, value: 10})
%{value: 32}
iex(6)> EventSourcedCalculator.V1.handle_command(%{value: 5}, %{cmd: :mul, value: 10})
%{value: 50}
Creating Your First Event-Driven Calculator
One thing to keep in mind is that we are going to need to keep the command and the event handler separate. The event is immutable and as such can only be derived by the events before it. We will build a handle_command that will return events, which will in turn change the state.
defmodule EventSourcedCalculator.V2 do
def handle_command(%{value: _val}, %{cmd: :add, value: v}) do
%{event_type: :value_added, value: v}
end
def handle_command(%{value: _val}, %{cmd: :sub, value: v}) do
%{event_type: :value_subtracted, value: v}
end
def handle_command(%{value: _val}, %{cmd: :mul, value: v}) do
%{event_type: :value_multiplied, value: v}
end
def handle_command(%{value: _val}, %{cmd: :div, value: v}) do
%{event_type: :value_divided, value: v}
end
def handle_event(
%{value: val},
%{event_type: :value_added, value: v}
) do
%{value: val + v}
end
def handle_event(
%{value: val},
%{event_type: :value_subtracted, value: v}
) do
%{value: val - v}
end
def handle_event(
%{value: val},
%{event_type: :value_multiplied, value: v}
) do
%{value: val * v}
end
def handle_event(
%{value: val},
%{event_type: :value_divided, value: v}
) do
%{value: val / v}
end
end
The handle_command produces events and the handle_event will take the event and augment the state of the calculator. You might want to start to add in some timestamps and metadata but we will save that for later. Let’s test it out.
iex(7)> c("calculator/event_sourced_calculator.v2.ex")
[EventSourcedCalculator.V2]
iex(8)> evt = EventSourcedCalculator.V2.handle_command(
...(8)> %{value: 42}, %{cmd: :add, value: 10})
%{value: 10, event_type: :value_added}
iex(9)> state = EventSourcedCalculator.V2.handle_event(%{value: 9}, evt)
%{value: 19}
A couple things to keep in mind here. We passed a value of 42 to the handle_command but that was not needed. Also when we passed the event we were saying that we want to add 10 to the current state. One needs the state to validate the command the other needs the state to produce a state.
Handling Errors by Modeling Failure
Right now the code will work but it will break with a simple divide by zero.
ex(4)> evt2 = EventSourcedCalculator.V2.handle_command(%{value: -1},
...(4)> %{cmd: :div, value: 0})
%{event_type: :value_divided, value: 0}
iex(5)> state2 = EventSourcedCalculator.V2.handle_event(%{value: 9}, evt2)
** (ArithmeticError) bad argument in arithmetic expression
es_calc_v2.exs:31: EventSourcedCalculator.V2.handle_event/2
Let’s take care of some of the edge cases.
defmodule EventSourcedCalculator.V3 do
@max_state_value 10_000
@min_state_value 0
def handle_command(%{value: val}, %{cmd: :add, value: v}) do
%{event_type: :value_added, value: min(@max_state_value - val, v)}
end
def handle_command(%{value: val}, %{cmd: :sub, value: v}) do
%{event_type: :value_subtracted, value: max(@min_state_value, val - v)}
end
def handle_command(
%{value: val},
%{cmd: :mul, value: v}
)
when val * v > @max_state_value do
{:error, :mul_failed}
end
def handle_command(%{value: _val}, %{cmd: :mul, value: v}) do
%{event_type: :value_multiplied, value: v}
end
def handle_command(
%{value: _val},
%{cmd: :div, value: 0}
) do
{:error, :divide_failed}
end
def handle_command(%{value: _val}, %{cmd: :div, value: v}) do
%{event_type: :value_divided, value: v}
end
def handle_event(
%{value: val},
%{event_type: :value_added, value: v}
) do
%{value: val + v}
end
def handle_event(
%{value: val},
%{event_type: :value_subtracted, value: v}
) do
%{value: val - v}
end
def handle_event(
%{value: val},
%{event_type: :value_multiplied, value: v}
) do
%{value: val * v}
end
def handle_event(
%{value: val},
%{event_type: :value_divided, value: v}
) do
%{value: val / v}
end
def handle_event(%{value: _val} = state, _) do
state
end
end
We added some checks and some pattern matching we also added in some hard limits to the values that we can see on the calculator. Let’s see it in action.
[EventSourcedCalculator.V3]
iex(11)> EventSourcedCalculator.V3.handle_command(
...(11)> %{value: 9500}, %{cmd: :add, value: 650})
%{value: 500, event_type: :value_added}
iex(12)> EventSourcedCalculator.V3.handle_command(
...(12)> %{value: 9500}, %{cmd: :div, value: 0})
{:error, :divide_failed}
We can see that the new handle_command was triggered and we didn’t return and event. This can be good or bad depending on the needs of the event sourcing. Let’s now talk about the first event sourcing law: “All Events Are Immutable and Past Tense”
When working with events you need to understand that the validation needs to happen in the command handler. In the authors opinion if there is no immutable change you should not model it.
We will now talk about how similar repeating events and keep the state is very similar to a fold. Let’s set up a series of commands and then run them together.
[EventSourcedCalculator.V3]
iex(16)> cmds = [%{cmd: :add, value: 10},
...(16)> %{cmd: :add, value: 50}, %{cmd: :div, value: 0},
...(16)> %{cmd: :add, value: 2}]
[
%{value: 10, cmd: :add},
%{value: 50, cmd: :add},
%{value: 0, cmd: :div},
%{value: 2, cmd: :add}
]
iex(17)> initial = %{value: 0}
%{value: 0}
iex(18)> cmds |> List.foldl(initial,
...(18)> fn cmd, acc -> EventSourcedCalculator.V3.handle_event(acc, EventSourcedCalculator.V3.handle_command(acc,cmd)) end)
%{value: 62}
We now can run through any series of events and then see the resulting output. We just need to get the event from the handle_command then send that to the handle_event. Also remember the last handle_event from V3. There is a catch all to be sure that even a bad event will still maintain the state so we can pass it to the next event.
Working with Event Sourcing Aggregates
We have been building an aggregate. An aggregate is a single, uniquely identifiable entity that has state, validates commands, and emits events.
One thing to keep in mind about an aggragate is that it needs a unique key that differentiates one from an other. In the idea of BankAccount the field account_number will helps us to do that. There are 2 characteristics of an event sourcing aggregate:
• Validates incoming commands and returns one or more events
• Applies events to state to produce new state
• Application of events and commands is pure and referentially transparent
By pure we mean that the function has no side effects. Transparent means that the same result will come from the same input.
Wrapping Up
We now have the building blocks to keep everything in a standard system.