Hello! I’m starting a series of posts about Aecor — a library for building eventsourced applications in Scala in purely functional way.
My ambitious plan includes not only providing a comprehensive walkthrough for this great tool, but also:
- discuss common event sourcing topics and how Aecor approaches them;
- explain how Aecor works under the hood;
- and, of course, build a working app! 🙂
Aecor is more than 2 years old and is written entirely by Denis Mikhaylov (@notxcain). I’ve been following the project since it’s early days, and recently got a chance to work at Evotor, where Denis’es team runs couple of dozens of Aecor-based services in production.
It’s super exciting to see advanced FP projects like this one being deployed in real production for a real business. What might look as a playground with a pile of fancy FP constructs, is actually a battle-tested solution with a well-thought, clear and composable interface.
Unsurprisingly, Aecor has always been one of the early adopters for cutting edge FP tech in Scala. While reading it’s code, you can find a lot of idiomatic and powerful applications of cats, cats-effect, fs2, and other Typelevel libraries. I should also mention Tagless Final pattern, which Aecor leverages in truly interesting ways.
All that power is used to give you another thing, that never ceases to fascinate me, which is Event Sourcing. A lot has been written about this technique and it can give you unmatched powers in many contexts. You definitely shouldn’t eventsource everything, but when you have an entity that is a good fit — Aecor will do most of the heavy lifting for you.
I’ve been into Event sourcing in Scala for several years now, doing it both as a hobby and professionally. Although I can’t call myself an expert, I can still fully appreciate amount of knowledge and effort that Denis put into Aecor.
Now that I’ve had some exposure to Aecor in production, I’m even more excited about it. Working in a team, extremely experienced in running eventsourced apps, I’m also learning a lot every day. And this is probably a good time for a post series 🙂
What Aecor gives you
Series is structured around capabilities, that Aecor gives the developer, so let’s briefly mention them.
One of the most exciting parts of event sourcing is defining behavior. I believe, that when designing software, behavior is what you should start with. Focusing on behavior instead of database schema is also in the roots of Domain Driven Design, and Aecor follows that principle.
Specifically, Aecor provides a set of MTL-style typeclasses, that can be composed to define different flavors of eventsourced behaviors. We’ll see how it works in detail in Part 1 and Part 2 of the series.
Next, you would probably want to run your behavior somehow. The whole scalability part of event sourcing is based on the ability to have small isolated islands of strong consistency. In simple words, you need a guarantee that for any single entity there’s no concurrent command processing. This is known as the Single Writer Principle, and in distributed system it requires consensus.
When you need consensus, the only Scala-native answer is Akka-cluster. It’s sharding module is a perfect fit for scalable eventsourced system. Aecor allows you to launch your behaviors on top of akka-cluster, and in Part 3 of the series we’ll find out how to do it, as well as:
- how Aecor isolates your purely functional and typed code from not so functional and typed Akka actors;
- what advantages Aecor runtime has, comparing to akka-persistence (Akka’s own event sourcing solution)
- alternative ways of implementing single writer, specifically an ongoing R&D around Kafka-based runtime, where consensus is delegated to Kafka partitions.
Parts 4a and 4b of the series are about building blocks for CQRS, that you get with Aecor. It’s well known, that CQRS is a natural fit for event sourcing. So it would be strange for an event sourcing toolbox not to have a couple of CQRS screwdrivers.
In this section we’ll use projections to build a streamed view of our entity.
Part 5 is not directly related to Aecor. We’ll discuss the Process Manager pattern, which is a very powerful tool to orchestrate eventsourced entities and other parts of the system. It fits naturally into Aecor-based apps, so I decided to dedicate a separate chapter to it.
By this time you’ll know everything to build solutions with Aecor. So it will be a good time to take a look under the hood: in Part 6 we’ll take Aecor apart gear by gear to see how it works and discuss design choices made.
What we’re going to build
Usually event sourcing examples are about transferring money or doing e-commerce. Instead, we’ll build a simple concert ticket booking system. Although real systems are times more complex, we’ll try to implement some interesting non-trivial business rules. This is by no means a guide to building booking systems — requirements are artificially crafted and may look awkward to real domain experts. But they suit well to the purpose of the series, which is to demonstrate Aecor on a not too trivial app.
You can check out the finished solution for this series in the github repo. Follow readme instructions if you want to launch it and play around (or try to break it).
Just to get you started, here’s how to wire up Aecor to your build (we’ll discuss specific modules later in the series) :
val aecorVersion = "0.18.0"
libraryDependencies ++= Seq(
"io.aecor" %% "core" % aecorVersion,
"io.aecor" %% "schedule" % aecorVersion,
"io.aecor" %% "akka-cluster-runtime" % aecorVersion,
"io.aecor" %% "distributed-processing" % aecorVersion,
"io.aecor" %% "boopickle-wire-protocol" % aecorVersion,
"io.aecor" %% "test-kit" % aecorVersion % Test
Also make sure that partial-unification flag is turned on.
And that’s it for the introduction. See you in the Part 1, where we’re going to define behavior for our booking entity.