Skip to main content

Motivation

In the modern world data storage is an inseparable component of most applications. There are various problems crossing the same issue of having efficient data storage(caching, application state management, databases, etc.).

At a larger scale it becomes more and more important to spend the minimum cost for keeping and managing such a storage. On the other hand, the centralized data management will cause performance issues and delays for data updates. Imagine an application with more than 10K consumers for the same data, or updating 1K different subject at the same time. The comminity has tackled the data management problem with a lot of different technologies, such as Redux, Mobx, etc. Unfortunately, none of them has designed to be cost effective at a larger scale...

The Event Storm has been created to solve the above mentioned problem.

Diving deeper

The state manager must provide a straight forward way to access, subscribe and modify to the data. Let's consider a centralized data manager such as Redux(thought the same is true for other data managers too, Redux will be used as commonly recognized goto solution). Centralizing the data in one place have a good advantage of having a single place of data modification. The main downsides of this approach is keeping the information as a single entry. If the data is single entry means it automtically becomes the subject in which the subscription can run. Rephrasing the last, each susbcription to a centralized data store is a subscription to whole store.

The problem

Subscription

Here comes the pain. When the data inside your centralized data store system is not about just the same business entity(of course it doesn't), you'll need some essential mechanisms to exclude unnecassary subscription calls. Let's build a simple example to demonstrate the problem.

import { createStore } from "redux";

const actions = {
increment: "counter/incremented",
decrement: "counter/decremented"
};

const defaultState = { value: 0 };

function counterReducer(state = defaultState, action) {
switch (action.type) {
case actions.increment:
return { ...state, value: state.value + 1 };
case action.decrement:
return { ...state, value: state.value - 1 };
default:
return state;
}
}

export { actions };
export default createStore(counterReducer);

The example is working correct - each click updates the state.value, and the button rerenders with the new state.value.

Now let's add a different component, that will not use the state.value, but a different portion of the state. age: 17, name: "John"

import { useSelector } from "react-redux";

function NotSubscribed() {
const { age, name } = useSelector(state => {
return { age: state.age, name: state.name };
});
console.log("render", age);
return (
<span>
{age} - {name}
</span>
);
}

export default NotSubscribed;

In the above example on each button click, when the state.value is changed, the NotSubscribed component will also rerender. The rerender is an unnecassary. Here it comes to the essential mechanisms 💊.

  • You can consider using React.memo, but unfortunetly it will not work.
components/NotSubscribed.jsx
export default React.memo(NotSubscribed); // ❌
  • Redux useSelector supports second argument, an equality function. Let's pass lodash.isEqual.
components/NotSubscribed.jsx
import isEqual from 'lodash/isEqual';

const { age, name } = useSelector(state => {
return { age: state.age, name: state.name };
}, isEqual); // ✅

Comparing the equality saves the rerender(essential mechanism N1). But what actually happened? Equality function needs previous and next states to compare. So far, we are yet computing the selector, but we don't rerender the component. Evidance:

components/NotSubscribed.jsx
import isEqual from 'lodash/isEqual';

const { age, name } = useSelector(state => {
console.log('computing the selector');
return { age: state.age, name: state.name };
}, isEqual);

What else we can do here?

  • You can try using useCallback for the selector function. Again you'll have no luck.
import { useCallback } from 'react';

const { age, name } = useSelector(useCallback(state => {
console.log('computing the selector');
return { age: state.age, name: state.name };
}, []), isEqual); // ❌
The example source

Here is the complete source code of this example.

So far you can live with the situation that it will run anyway. This basically means all the state management touch points, will be triggered on any state update. You can start optimizing the complex selectors in the application to save some comuting time(e.g. using reselect as an essential mechanism N2). Why is it like this? Having a centralized data store, has no relation with the the data kept, has to deal with it as a single entry. The data store and data relation is crucial(Event storm provides an alternative solution).

Data ownership

In combination with some UI solutions(e.g. React), data store is highly recommended. In most of the cases the rendering solution has no native integration with any kind of data store. That far the data store must find a solution te be integrated with the UI solution. Rephrasing the last sentence - The data store must have a mechanism to trigger a UI update when integrated with speficied UI solution. Redux data store has official support of integration with React UI library. The Redux is forcing to set a Contenxt.Provider. From that point the data access, from any UI component will be managed by React not Redux. This results in some difficulties:

  • Having multiple store's at the same time. As Redux is a centralized data store, it's conter conceptual
  • Accessing the store data non from UI component. E.g. sending data store to backend each specified time interval.
  • Code splitting. At a larger scale(or just with microservice architecture) it's mostly impossible to decouple the application into independent peaces, without a relation on data store. Main issue with this is again centralization and data access. Imagine a situation when you want to lazy load a theme data store to your application.

The main issue here is the transfer of data ownership. Redux transfers the ownership to React. And the difficulties with React as a data store is trivial: it's a UI solution, and was never designed to be a data store.

What Event Storm can suggest?

Event Storm is designed to address and solve the above mentioned problems. It provides design minimals to build any size of data store. The Event Storm library is suggesting a decentralized store with a single usage interface as before.