When searching for tutorials about how to start with Event Sourcing (ES) with a given library or framework, usually you can find something similar to:

These examples are perfectly fine for a demo or a POC. Unfortunately, if you start using them in a real project and develop your application in a similar way, you can very easily fall into a nasty trap. What’s wrong with the snippets above?

Let me give you a hint. How about this snippet:

We already know (or should know) that mixing persistence code with domain code is not the best style, and should be avoided (we are not talking about simple CRUD applications). As Uncle Bob said some time ago, your persistence mechanism should be a plugin to your core domain. If you agree with that, then you probably see the problem with the ES implementation above. Technical stuff, like Persistent Actor, Persistent Entity, framework imports are blended with domain code.

At first glance, you might say: so what? I don’t see any problems with that. Exactly, you will face them a long time from now, usually when your application is in production, your codebase is complex, and it is simply too late to revert some decisions, like the choice of ES framework.

Packaging

How to fix this? Easy, use proper packaging, and keep your domain code completely free of any ES framework/library dependencies.

The domain protocol is pretty straightforward: commands, events, and exceptions (could be also replaced by case classes, but let’s keep it plain):

If you think about ES from a domain point of view, it’s dead simple. You just need to provide 2 methods on State :

process(command: Command): List[Event]

apply(event: Event): State

On the other hand, if you check the UserActor implementation, you might be a little bit overwhelmed with the complexity of Persistent Actor in this very simple use case. “This is nothing”, in reality a production-ready Persistent Actor will be even more complicated. Of course, you could increase code readability, but you cannot escape from technical complexity, you need to handle things like: fail-over, recover, snapshotting, debugging, etc. The difference is that any changes at this level don’t require any additional work with the domain code.

More key points:

Technical messages (necessary for communication with Persistent Actor) like UserActorResponse , UserActorQuery are defined on application level. At this point, there is no need to keep them at domain level. Commands and events are a part of the domain. They could also be a part of Persistent Actor API, but never the other way. Commands/events validation should be done at domain level. Some validation could be duplicated on different layers, but the domain should rule them all. Domain, because it’s free of any technical dependencies, is extremely easy to test (although you won’t find tests in the repository for this layer). Most importantly, at any point in time you can switch from Akka Persistence to any other ES solution, or create your own one.

Library vs framework

Just to be clear, I’m not against any particular framework or example mentioned above. Maybe I’m just a little bit annoyed with an Aggregate (which is a DDD thing) full of technical dependencies. Sadly, a popular assumption is that this is a recipe for a domain Aggregates implementation. Context is the key, so remember that those are simple examples, that will help you to learn the framework and create something working really fast. A production ready solution that will be maintained (possibly) for years, is a completely different story.

Before you choose an ES solution from the market, check if you could easily separate your domain. A good example could be Akka Persistence vs Lagom. Clean domain in Akka Persistence is very easy to achieve. You could treat it like another, replaceable library. On the other hand, Lagom, which is a full blown framework, build on top of Akka Persistence, forces you to implement ES in a particular way. It’s possible to have clean domain in Lagom, but in a long term it will be just a fight with the framework, which is pointless.

Are ES frameworks bad? Even if they perfectly fit to my use case? Of course not, but as with any framework, sooner or later you will need to implement a use case that is not fully supported by it. Also, the release and support lifecycle of a framework is not as fast as in case of a library. I’m still waiting for a fix for this issue, where in Akka Persistence I could use the newest akka-persistence-cassandra since the release day.

Bonus

From my experience, adhering to package rules might be a challenging task. It requires long term discipline and careful PR review. Fortunately, you can semi-automate this process by using ArchUnit, and add a few simple architecture validation rules to your project: