Doing CQRS + Event Sourcing without building a spaceship
--
No one likes to put it in their CV but most of a web developers’ work still is about doing CRUD operations on databases. That’s a straight forward job in most cases but now and then managing the state of more complex models can be a bit difficult. A good technique that can help you reduce state management complexity is to separate how you persist and retrieve data from the database, as done by the CQRS pattern. Which is just a fancy way of breaking the problem into smaller parts.
A bit of a misconception about CQRS is that people relate it straight away to how the Java and C# community absorbed it with their enterprise finesse. But it’s not the only way of doing it, as Martin Fowler wrote on his post about CQRS “At its heart is the notion that you can use a different model to update information than the model you use to read information.”
Event Sourcing complements CQRS in the sense that it can take care of the write side, as written on CQRS and ES Deep Dive by Microsoft “If you capture changes in your write model as events, you can save all of your changes simply by appending those events to your database or data store on the write side using only Insert operations.”
First of all the most valuable learning I take from these techniques is the ideia of event streams being used to generate different projections/interpretations of its data. This is a pretty cool concept and brings a lot of possibilities to software development. A good analogy for event processing is a prism where the white light is broken into its constituent colours.
So these are the 3 fundamental parts I’ll be playing with:
- Event Stream: sequence of events produced by the system.
- Projections: are event consumers, they process events from the event stream.
- Read Models: it’s the cached result of the event processing by the projection.
The main objective being to generate read models from the event stream.
CQRS + ES is very appealing, what could we learn from it?
- Read and write separation — OK, no way around it, there can be some serious disparity on how you do reading and writing, which can tear a database model apart.
- Having a stream of events where events that can be used to create projections of its data — also OK, beautiful concept.
- Having to adopt full blown frameworks or building spaceships to do event processing — NOT going to happen.
The ideal solution in my opinion needs to be simple and doable with whatever technology you currently have. The mindset shift from conventional Data Model to event-based programming is a lot of change already.
Am I asking too much? Maybe, but what’s the problem in playing with it a little bit and having some fun?
JS/Flux/Redux have taught us all some good things about state management, I’m pretty sure we can add it to the mix and simplify event processing further, bringing it down from the over engineering realm. That’s how Binocular was born, a little project that plays with these concepts. It combines the observer pattern with reducers to create something different.
Why Binocular as project name?
Like CQRS, binocular vision happens when two separate images from two eyes are successfully combined into one image in the brain. CQRS has two eyes: the read and write eyes 😉.
What is the mission?
- Do it in PHP because that’s the language I love.
- Write events in a cheap, immutable, append only fashion.
- Use projections to read from the event stream and calculate read models state.
- Be able to change how events are processed without breaking anything.
The ingredients
- Event: represents something that has already happened, has a root ID property the projections will use to aggregate the events, and also has actions to be applied by the reducers. It’s actions in the plural because an action can have many versions.
- Action: has a name and version, does the mapping between reducers and events.
- Projection: as consumers they interpret and extract information from the events stream. Has the reducers that will change the state of the read models. For simplicity sake projections are 1–1 with read models.
- Reducers: basically an array of versioned callables that live in the projections. Because events are versioned, reducers are also versioned, it’s a form of mapping. If a new version of the event is created a new reducer version also needs to be created to process the changes.
- Event Repository: persists and retrieves events.
- Read Model Repository: persists and retrieves read models.
Justifications
There’s no connection between events and projections
Binocular does not provide a way of “wiring” projections and events, instead the provided base projection class has an event repository as dependency where it can fetch and replay events to calculate state. This was a conscious decision to not reinvent the wheel as most PHP frameworks offer an implementation of the observer pattern, just use what the framework offers. An easy way to combine Binocular with the observer pattern is to persist events before they are fired/queued and use projections in the listener class to calculate de state of its read model.
Reason for 1–1 relationship between read models and projections
Forcing them to be 1–1 makes it simpler to reason at the expense of less flexibility. One projection should only calculate the state of one read model.
Thanks for reading! ❤
P.S.