DEV Community

loading...
Cover image for Server-side Redux. Part II. The Design.

Server-side Redux. Part II. The Design.

Valerii Udodov
Originally published at valerii-udodov.com ・6 min read

The State Management Goes Wild

This is the second article of the series where we will try to find out if there’s a place for Redux on the other side of the fence.

Even though this series is base on the assumption that you are more or less familiar with what is Redux, don’t worry if not, as we covered all necessary concepts in the previous article. Take your time and make yourself comfortable with Redux.

Roots

In the previous article, we witnessed, how Redux can drag responsibility of being a source of truth for the state, manage its distribution and help multiple independent components stay in sync. A good example would be a scenario that forced Facebook to come up with something like Flux in the first place: a large facebook message view, where you can have three independent components depend on whether you read a message or not. Main view, compact view in the right corner and icon-counters on the top.

With the help of Redux, the state distribution in this sort of application will look as following

Alt Text

Front-end Redux state distribution

Moving things around

The Whys

Now, finally, we are reaching the important point of this series, where we will answer the question: why would the one possibly want to use Redux somewhere away from the front-end? 🤯

Pretty much for the same reason why you would use it in the front-end... Single source of truth for the state, its distribution, and centralized management. Even though the idea might make sense, the scenario is not very clear yet.

The Scenario

Let's get back to the Facebook case study first. The root cause of the problem there was the data synchronization between components without a direct connection. The more dependent nodes appeared, the more convoluted distribution tree became. Complexity was growing exponentially.

Imagine replacing components with front-ends. Front-ends that are simultaneously working and interacting with the same state. It might be the same client-side application in different browser windows as well as absolutely different front-end applications. Main criterion: they need to interact with the same state.

The following diagram represents a conceptual state distribution graph for such an application. The left side and right side are separate React front-ends with a Redux-equipped server in the middle. One of the right-side front-end components performs a change in the state (green circle), change (action) delivered to the server where it is dispatched to the Redux Store. The reducing function performs all necessary state changes and finally, the new state got delivered back to the front-ends. In this case, we are using the top-level component distribution model in both front-ends to deliver the state to the dependent components (yellow circles).

Alt Text

Back-end Redux state distribution

The Hows

So far so good, it makes sense and everything seems to logically fit. However, the vague point here is state exchange.

In a regular React application, everything is happening inside the browser in the same JavaScript runtime, allowing real-time bidirectional communication between Redux and React. Moving Redux to the back-end introduces physical segregation between the React root component and Redux Store. The black dashed line on the diagram above illustrates the physical segregation of those two. To get Redux to work as we expect it to, we must make the communication as seamless as it is in its native habitat.

The Challenge

The first thing that comes to my mind when I look at the border between the front-end and back-end is HTTP. But will it do the job here? To answer this question let's first figure out what problem we are trying to solve.

We need to establish real-time, bidirectional communication between the Redux Store and root node of each and every React front-end. It means that both the client and server should have the ability to push information equally.

HTTP vs WebSocket

This topic on its own deserves a separate article. To save some time and not lose the focus, I'll say that HTTP out of the box supports server-push approach with Server-Sent Events (SSE) and client JS has built-in support for it, thanks to HTML5. On top of it, HTTP/2 can utilize a single TCP connection to deliver multiple messages in both directions, making it a full-duplex, bidirectional connection.

However moving forward I chose WebSocket as a protocol that was specifically built for this sort of communication, it does not bring unnecessary data overhead, that HTTP brings (e.g. Headers). Additionally, WebSocket is a more generally known way of solving this sort of task.

Now all what is left is to connect right dots with right lines.

Client

As we discussed, we will take the root-node data distribution approach. This means that the root node will receive the whole state and propagate it all the way down through the props. It should happen every time the "push" event arrives from the server.

Now we also need to initiate state updates from the client. We decided on the delivery mechanism, but we did not decide on what we will deliver. Actually Redux already solved this problem for us. As we know Redux uses actions to operate on its state tree. There's no reason for us to change that, even though we increased the distance slightly. All we need to do is to define a dispatch(action) callback which will push actions back to the server. So that any component in the tree can push actions to the server (remember the green circle).

Server

To consume "push" events on the client we first need to produce them. Every time a new state is produced by the reducing function, the server must initiate a "push" event. And finally, we need to handle incoming actions from the client.

To push state we can use Redux listener callbacks, which will be executed on every attempt to change state, disregards whether it was changed or not. At any point in time, we can request a new state and utilize WebSocket to deliver it to the client.

Process actions is dead simple. Once we receive an action, we directly dispatch it with Redux Store.

Final Design

This is it. We have everything on its places, we have a way to deliver actions to the Redux Store as well as a subscription mechanism to update all front-ends on every state change.

The final design looks as following

Final Design

What's next?

Optimization

You might think that sending state every time to all the clients is not the most efficient approach. And you are right. But is it a problem? Wether it is or not, it really depends how big is your state tree. If it is reasonably small, I would not bother. If it is big enough and you are worried about some clients' latency, you can decrease the data noise, e.g. by sending only state delta.

Redux everywhere

As another possible design iteration, nothing stops you from having Redux on the front-end if you feel like it is required, this will change your state distribution diagram to something like this

Back-end state distribution

The state source of truth still stays on the server, but client-server communication happening between server-side Redux and client-side Redux and now client-side state propagation lays on the shoulders of the client-side Redux.

Conclusion

Unicorn

Redux on the backend is not a magical unicorn, not just a theory and we will prove it in practice.

This kind of architecture is not for the "daily use", but it is not just for fun either. It will work very well in most of the real-time applications, particularly for things like chat apps or online games.

It absolutely agnostic to the front-end complexity and can work well with simple state-waterfall applications as well as complex front-ends with Redux as a state manager.

Anyhow, it is time to get hands dirty and try discussed architecture in practice.

Discussion (0)