Event Sourcing in Go: the Event Handler

How to compute the current state of a resource in an event-sourced Go application?

Pierre Prinetti
3 min readFeb 10, 2018
Under a yellow neon - Hackescher Markt, Berlin

This post comes from my blog: https://pierreprinetti.com/blog/2018-event-sourcing-in-go-the-event-handler/

Recently, I have been working on an event-sourced application built around Command-Query Responsibility Segregation (CQRS). My job was to figure out how to implement a new command in Go.

In order to act on a given object, we have to build the current state of that object out of the events that created and modified it. Here I want to show how I have come up with the applier interface to represent the Event logic.

The Event Store

Let’s keep the architecture as simple as it can be: a Go service responds to the external call and directly operates on the events.

The collection of events, namely the Event Store, can be a SQL table that looks like this:

The property aggregate_id uniquely identifies every resource in the store.

How to build an aggregate

Martin Fowler, in his article on Event Sourcing, models two different responsibilities in handling events:

  • Processing domain logic: the logic that implements the change (e.g. func UpdateUser(userAggregateID, newUser))
  • Processing selection logic: the logic that maps the event named user.updated to the function UpdateUser.

According to Fowler, a good place to put the Processing domain logic in is the Domain Model.

The event data would be fetched from the persistence and sent to a matcher function (selection logic), that would itself call the Processing domain logic which is coded as a method of the User object.

Let’s focus on the Processing domain logic: I suspected there had to be a more idiomatic shape for the event logic in Go. I started my investigation by asking myself:

What is an event?

An event is a state change. The event, in our Event Store, is no more and no less than the flattened representation of a behaviour, waiting to be applied to a specific object.

What would be the most scalable way to define a behaviour in Go? Interfaces!

This applier interface defines the behaviour of an event: it is defined as a function that changes the state of the domain object it refers to.

We can have separate logic handling the connection to the Event Store and the Processing selection logic, fetching events from the Event Store and returning them as types implementing the applier interface.

This is how simple the Aggregate builder can look like:

Validation

How do we validate the change request?

I can think of 3 different types of validation:

  • Payload validation. Rules I can enforce just by looking at the request payload: required fields, value types, ranges, enums, and payload internal consistency. Example: the favourite IDE has to be picked from a list.
  • Request validation. Rules that refer to the state of the domain object before the change. If a user has been deactivated, he should not be able to change his favourite editor.
  • Domain object validation. Is the resulting domain object in a legit state after the change is applied? Example: a user can only have Xcode as his favourite editor if he’s using a Mac.

In the applier-featured model, the most obvious piece of code that has access to the three states (the change payload, the User before, the User after) is the Applier itself. The Apply function will run all the required validations, and possibly return an error.

The event would then be applied carefully handling the returned errors.

This code shows how decoupled the event logic handling can be from the data model, and how it offers pretty nice boundaries for our juicy unit tests.

A further iteration could make Apply accept an interface as an argument, instead of a pointer. For this, I would need to add getter and setter methods on the User type. This is a painful step I am trying to avoid by keeping the User type very simple, and therefore not giving it too much reasons to change. As long as it only is a collection of properties, I don’t think I will need this new layer of abstraction.

This post originally appeared here.

--

--

Pierre Prinetti

Test-driven backend engineer. Cryptography consumer. Berlin, Europe.