May 4, 2015

Event sourcing in Clojure.

Recently I came across a blog post by James Nugent that described some example patterns for implementing event sourcing in Go. I'm no expert, I've not studied much Go code before and I've never written any but I'd certainly have to agree with the observation James made, there is much less code than some C# implementations.

James made available the code for his post on GitHub, so I thought it would be interesting to have a look and come up with a Clojure implementation based on the same domain. I'd suggest reading James post as this one will follow the same structure.

FWIW, the comparison between Go and Clojure is a bit of an odd one really, this is just a bit of fun.

Events

As per James's post we will use a simplified domain model of a frequent flier. James uses mutable Go structures to define his events, we can make use of immutable Clojure records. We could probably just use Clojure maps but I'm planning to perform dispatch based on the event type, so a record or type is probably more suitable.

If we really wanted to use maps then we could probably attach some meta data to them using Clojures with-meta and then switch on that meta data. I don't think we need to worry about it though, Clojure records can be used as if they were maps in most scenarios.

Go
Clojure

Obviously the Clojure version of the events are dynamically typed, we could use type hints if we really wanted to.

Aggregates

In the Go example, James uses structs to represent aggregates and capture the state that will enable invariants during future behaviour, e.g. changing the frequent fliers status based on the stateful "tier points" value.

Go

Clojure

Its worth noting that the Go code above uses a Status enumeration to represent the frequent flier status, in the Clojure example I'll just be using the keys :red, :silver and :gold.

State transition and change tracking

Go
Clojure

In general the Clojure version of the code implements the same pattern as the Go code. We call a function on the aggregate, perform some invariant checking if applicable, track the event and apply the "state" transition.

The Go version of the code switches based on the type of the event being processed and applies the state mutations.

The Clojure version of the code uses a multimethod to dispatch based on the event type being processed, each chunk of "state" mutation is isolated in its own function. Of course, when we say mutation that's not true, since really we just return a new version of the aggregate with the updated "state".

The Clojure code benefits from having no mutable state, each of the functions are pure in nature. James does mention in his post that having immutable records can make for a more elegant implementation. I'm not sure the Clojure version is "more elegant" but I think its pretty nice.

Loading history

Go
Clojure

In the situation where you need to hydrate an aggregate using a collection of past events I think the Clojure implementation is pretty neat. All we need to do is (reduce transition ff history). We reduce the history of events using the transition function and an inital default state frequent flier. If we had a massive number of events we could probably even use Clojures fold reducer, this would carry out the reduce operation in parallel. We also assoc in an expected version based on the history.

Example usage

Go
Clojure

Both of the above examples hydrate a frequent flier from a history of events and print out the aggregate. They then record another FlightTaken event so that the accumulation of tier points causes an upgrade to gold status.

Conclusion

Have a look at both the Go version and the Clojure version and see what you think. I noticed a few things:
  1. Lisp is beautiful (subjective at best)
  2. Clojures immutability and built in support for things like map/reduce make things like hydrating an aggregate easy
  3. Multimethods are quite a consice way of dispatching based on an event type
  4. Go is a bit tricky to read but then again I am a total newbie

If you know Clojure then please let me know if I'm doing anything really stupid or not very idiomatic by sending a pull request.

I'm going to write a Clojure follow up post with a more complicated domain model and a storage implementation, probably using Event Store.

Tags: event sourcing clojure