Max Polun's blog

Comparing Event sourcing with redux

Background

I’ve long thought that event-sourcing (ES) was an interesting way to structure a back-end application, and I use redux very often in my day job as a front-end developer. Recently I’ve been looking more closely at event-sourcing and I think putting the information I’ve learned into front-end terms might be useful for others.

This is mostly for front-end engineers interested in event-sourcing. If you don’t know redux jargon, you might find this hard to follow.

What are these things?

Redux is a framework for managing state in javascript UI applications, generally used with react on the web, but it can work in other situations as well — code outside of redux (your react app for example) dispatches actions which are sent to reducers which return a new copy of the application state. How state is transferred out of redux depends on what you’re hooking it up to, but with React (the most common situation by far) connected components subscribe to the redux state, and use selectors to choose what parts of the state they’re interested in. Workflows that require more than just dispatching an action can use middleware, which can observe all actions and the state when actions are dispatched, and dispatch additional actions based off of that information, or external state. Common middleware includes the thunk middleware, which lets you write functions that can dispatch multiple actions asynchronously; sagas which are coroutines that react to actions being dispatched by dispatching other actions or executing code; and observables which allows similar things as sagas, but work via rxjs observables.

ES is bit less well defined — it’s a pattern not a framework, but generally your ES system receives commands, the system determines which aggregate receives that command, and reconstructs its state by replaying all events related to that aggregate (the aggregate can start from the beginning of its existence, or from a snapshot which requires fewer events to be replayed). The aggregate can then validate the command and emit new events which are saved to the event store, and emitted on the event bus. Queries are done via a projection which is a current model of the state, in the form that the query can most efficiently respond. Workflows that require coordination between multiple aggregates use sagas which listen to events on the bus, and can issue additional commands as required by the business logic, or react to external state.

Big picture

Both ES and redux can be thought of as ways of implementing the overall Command/Query Responsibility Segregation (CQRS) pattern — in this pattern you have totally different objects to make state mutations (Commands) and to read state (Queries). Making this separation gives you a lot of flexibility since often you want different guarantees from commands and queries. Both also have a similar strategy for making this separation — state changes take place using events. Events are nice for this purpose because they’re a pure data structure — you can easily serialize them, version them, record them, etc. Additionally both are fairly amenable to a purely functional approach, though neither strictly requires it.

There are some big differences though — redux mutates state in response to new events, but doesn’t store the events. ES stores events as the single-source of truth (there may be other stored state, but it’s disposable and can be rebuilt from the events). This could be thought of as a straightforward translation since redux is a framework for front-end UI apps, and ES is generally a server-side thing.

The biggest difference though is that ES is way more opinionated than redux. Redux is a general state-management system that can be used in a wide variety of situations — the only inherent downside is boilerplate. ES however is explicitly meant for distributed, eventually-consistent systems. You can still have an event stream (for e.g. auditing) without doing ES, but ES by design will lead to certain high-level trade offs which may or may not be reasonable. ES does bring advantages with that though — an ES system should be able to be distributed, and unlike keeping a separate event stream you know you can always process all events correctly since you need to be able to for your system to work.

Bad analogy time

If you want a short version of how to translate redux to ES, here’s a rough correspondence

ReduxES
ActionEvent
ReducerAggregate
SelectorProjection
Any external code that can dispatch multiple actions (Thunks, can be done in a hook, or React callback)Command
sagassagas

Let’s go into a bit more depth on how these are all bad correspondences

Events: unlike actions, events are not the inputs to the system, they are the outputs. So in redux, we dispatch actions in order to update the redux state. This is kind of like how an aggregate updates its internal state in response to events, but events are added to the stream by issuing commands, not by just any code in the application. We especially don’t want aggregates creating new events when applying events, because then we’ll get new events added every time we replay the events.

Aggregates: aggregates are kind of like reducers, but one big difference is that aggregates only receive events that are scoped to them. A reducer-like function is a totally reasonable way to implement an aggregate, but also keep in mind that an aggregate just determines the state for a single instance of the type of object an aggregate is, but a reducer can manage many objects of that type (an aggregate doesn’t have to correspond to a database table, or a single class, so you could have multiple children of an aggregate — all the comments on a blog post, for example)

Projections and selectors are pretty similar — they’re cached versions of the true data in a form close to how the UI wants it. The difference is just that like all redux stuff the selector is in-memory (and memoized if using reselect), while a projection is usually stored in some sort of datastore (optimized to be fast to retreive).

Commands are the biggest thing missing from redux — redux actions are a cross between events and commands, but since commands can produce multiple events they’re also like thunks, or if you don’t use thunks they’re also like callbacks that dispatch multiple actions. Much like thunks, commands can be used as a place for throwing in side-effects

Dealing with background tasks, or making commands in response to events that occur is a concern with both, and sagas are a solution that can be used with both. Redux-sagas are clearly influenced by sagas as used in ES, so that’s probably why.