Can I use Redux on the server?
- Transfer
Redux is an excellent tool for managing the state of complex front-end applications. The author of the material, the translation of which we are publishing today, is going to find an answer to the question of whether it is possible to use the Redux capabilities in a server environment.
The home page of the Redux library says that this is a “predictable state container for JavaScript applications.” Redux is usually referred to as an application state management tool, and, although this library is mainly used with React, you can use it in any JavaScript-based project.
We already mentioned that Redux is used to manage the state of the application. Let's talk now about what “state” is. This concept is quite difficult to define, but we still try to describe it.
Considering the “state”, if we are talking about people or objects of the material world, we strive to describe, in fact, their state at the time in which we are talking about them, perhaps considering one or several parameters. For example, we can say about the lake: “The water is very hot”, or: “The water is frozen”. In these statements we describe the state of the lake in terms of its water temperature.
When someone says about himself: “I am broke,” he considers the amount of money he has. It is clear that in each of these examples we are talking only about one aspect of the state of objects. But, in the example about money, the statement may be this, describing several parameters: "I broke, I have not eaten for a long time, but I am happy!". It is very important to note that the state is something non-permanent. This means that it can change. Therefore, when we learn about the current state of a certain object, we understand that its real state may change a few seconds or minutes after we have learned about it.
When we deal with programs, the concept of "state" is associated with some features. First, the state of the application is represented by data that is stored somewhere. For example, this data can be stored in memory (say, as a JavaScript object), but it can be stored both in a file and in a database, and using the tools of some kind of caching mechanism like Redis. Secondly, the state of an application is usually tied to its particular instance. Therefore, when we talk about the state of an application, we mean a specific instance of this application, a process, a working environment, organized in an application for a specific user. Application state may include, for example, the following information:
If we talk about the state of the application at a lower level, it may include, for example, the following information:
Looking at the “snapshot” (they are often called “snapshots” - from snapshot) of the application state at any time, we can find out in what conditions the application worked at that moment, and, possibly, if necessary, recreate these conditions by giving application to the state in which it resided at the time of snapshot.
The state can be modified during the execution of the user certain actions. For example, if a user correctly moves a game character in a simple game, this may increase the number of points. In quite complex applications, the approach to state modification may become more complicated, state changes may come from different sources.
For example, in a multiplayer game, how many points the user scores depends not only on his actions, but also on the actions of those who play with him on the same team. And if a computer-controlled character successfully attacks the game character that the user controls, the user may lose some points.
Imagine that we are developing a front-end application like PWA Twitter. This is a one-page application that has several tabs, say - Home, Search (Search), Notifications, and Messages (Messages). Each such tab has its own working area, which is intended both for displaying certain information and for its modification. All this data forms the state of the application. So, new tweets, notifications and messages come into the app every few seconds. The user can work with the program and with this data. For example, he can create a tweet or delete it, he can retweet a certain tweet, he can read notifications, send messages to someone, and so on. Everything that was just discussed modifies the state of the application.
All of these tabs have their own set of user interface components used to display and modify data. The state of an application can be affected by data entering the application from the outside, and user actions.
It is clear that in such an application, sources of state changes can be different entities, and changes initiated by different sources can occur almost simultaneously. If we manage the state manually, it may turn out that it will be difficult for us to keep track of what is happening. These difficulties lead to contradictions. For example, a tweet may be deleted, but it will still be displayed in the tweet feed. Or, say, a user may read a notification or message, but it will still be displayed in the program as unvisited.
The user can like a tweet, a heart appears in the program's interface, but a network request that sends information about the like to the server will not work. As a result, what the user sees will be different from what is stored on the server. It is in order to prevent such situations, and you may need Redux.
There are three basic concepts in the Redux library that are designed to make managing the state of applications simple and straightforward:
Using these three concepts means that the application should no longer directly monitor events that are sources of state changes (user actions, API responses, the occurrence of events related to receiving some data via the WebSocket protocol, and so on) and make decisions about how these events will affect the state.
Through the use of the Redux model, these events can trigger appropriate actions that will change state. Components that need to use data stored in the application state can simply subscribe to state changes and receive information of interest. Using all of these mechanisms, Redux aims to make changes in the state of an application predictable.
Here is a schematic example that demonstrates how you can organize a simple state management system using Redux in our fictional application:
Taking this code as a basis, we can equip our state management system of the application with additional actions and send them from different places of the application without risking hopelessly confused.
Here is the material from which you can learn more about the three fundamental principles of Redux.
Now let's talk about using Redux in a server environment.
We explored the Redux capabilities used in developing client applications. But, since Redux is a JavaScript library, it can theoretically be used in a server environment. Let us consider how the above principles can be applied on the server.
Remember how we talked about how the state of the client application looks like? It should be noted that there are some conceptual differences between client and server applications. Thus, client applications tend to maintain a state between various events, say, between the execution of server requests. Such applications are called stateful applications.
If they did not strive for state storage, then, for example, when working with a certain web service that requires entering a login and password, the user would have to perform this procedure every time he goes to a new page of the corresponding web interface.
Backend applications, on the other hand, tend to ensure that the state is not stored (they are also called stateless applications). Here, speaking of “backend applications”, we mainly refer to projects based on certain APIs that are separate from front-end applications. This means that the state of the system should be provided to similar applications each time they are accessed. For example, the API does not monitor whether the user is logged in or not. It determines its status by analyzing the authentication token in its requests to this API.
This leads us to an important reason why Redux would hardly be used on servers in the form in which we described its capabilities above.
The fact is that Redux was designed to store the temporary state of the application. But the state of the application stored on the server should usually be long enough. If you would use the Redux repository in your server-side Node.js application, then the state of this application would be cleared every time the process stops
The situation becomes even more complicated if we consider server applications in terms of their scalability. If you had to scale the application horizontally, increasing the number of servers, then you would have a lot of Node.js processes running simultaneously, and each of them would have its own version of the state. This means that if two identical requests to the backend were received simultaneously, different answers could be given to them.
How to apply the principles of state management discussed on the server? Take another look at the Redux concepts and see how they are commonly used in a server environment:
Consider two design patterns that, by their nature, are similar to what the Redux library is aimed at. This is CQRS and Event Sourcing. They, in fact, appeared before Redux, their implementation can be extremely difficult, so we will talk about them very briefly.
CQRS (Command Query Responsibility Segregation, division of command and query responsibility) is a design pattern that, when implemented, an application reads data from the repository only with queries, and writes only with commands.
When using CQRS, the only way to change the state of an application is to send a command. Commands are similar to Redux actions. For example, in Redux, you can write code that corresponds to this scheme:
When using CQRS, something like this will look like this:
Queries are the mechanisms for reading data in the CQRS template. They are equivalent to design
The Event Sourcing template (event registration) is designed with a view to registering all changes in the application state as a sequence of events. This template is best suited for applications that need to know not only about their current state, but also about its history of changes, about how the application has reached its current state. As examples here you can cite the history of operations with bank accounts, tracking parcels, working with orders in online stores, organization of transportation, logistics.
Here is an example of implementing the Event Sourcing pattern:
What happens here also reminds us of working with Redux actions.
However, the event registration mechanism also organizes the long-term storage of information about each change in state, and not just the storage of the state itself. This allows us to reproduce these changes to the point in time we need, thus restoring the contents of the state of the application to this point in time. For example, if we need to understand how much money was in a bank account on a certain date, we only need to reproduce the events that occurred with the bank account until we get to the desired date. Events in this case are represented by receipts of funds to the account and debiting them from it, writing off a bank commission and other similar operations. In the event of errors (that is, in the event of events containing incorrect data), we can invalidate the current state of the application,
CQRS and Event Sourcing templates are often used together. And, interestingly, Redux is, in fact, partly based on these templates. Commands can be written so that when they are invoked, they send events. The events then interact with the repository (database) and update the state. In real-time applications, query objects can also listen for events and receive updated status information from the repository.
Using any of these templates in a simple application may unnecessarily complicate it. However, in the case of applications built to solve complex business problems, CQRS and Event Sourcing are powerful abstractions that help better model the subject area of such applications and improve their state management.
Note that the CQRS and Event Sourcing templates can be implemented differently, with some of their implementations being more complex than others. We have considered only very simple examples of their implementation. If you are writing server applications in Node.js, take a look at wolkenkit . This framework, among those that have been discovered in this area, provides the developer with one of the simplest interfaces for implementing CQRS and Event Sourcing templates.
Redux is a great tool for managing application state, in order to make state changes predictable. In this article we talked about the key concepts of this library and found that, although using Redux in a server environment is probably not the best idea, similar principles can be applied to the server using the CQRS and Event Sourcing templates .
Dear readers! How do you organize the management of client and server applications?
Why do you need Redux library?
The home page of the Redux library says that this is a “predictable state container for JavaScript applications.” Redux is usually referred to as an application state management tool, and, although this library is mainly used with React, you can use it in any JavaScript-based project.
We already mentioned that Redux is used to manage the state of the application. Let's talk now about what “state” is. This concept is quite difficult to define, but we still try to describe it.
Considering the “state”, if we are talking about people or objects of the material world, we strive to describe, in fact, their state at the time in which we are talking about them, perhaps considering one or several parameters. For example, we can say about the lake: “The water is very hot”, or: “The water is frozen”. In these statements we describe the state of the lake in terms of its water temperature.
When someone says about himself: “I am broke,” he considers the amount of money he has. It is clear that in each of these examples we are talking only about one aspect of the state of objects. But, in the example about money, the statement may be this, describing several parameters: "I broke, I have not eaten for a long time, but I am happy!". It is very important to note that the state is something non-permanent. This means that it can change. Therefore, when we learn about the current state of a certain object, we understand that its real state may change a few seconds or minutes after we have learned about it.
When we deal with programs, the concept of "state" is associated with some features. First, the state of the application is represented by data that is stored somewhere. For example, this data can be stored in memory (say, as a JavaScript object), but it can be stored both in a file and in a database, and using the tools of some kind of caching mechanism like Redis. Secondly, the state of an application is usually tied to its particular instance. Therefore, when we talk about the state of an application, we mean a specific instance of this application, a process, a working environment, organized in an application for a specific user. Application state may include, for example, the following information:
- Did the user log in or not? If so, how long does the session last and when will it expire?
- How many points did the user score? Such a question is relevant, for example, for some kind of game.
- Where exactly did the user pause the video? This question can be asked about the video player application.
If we talk about the state of the application at a lower level, it may include, for example, the following information:
- What variables are set in the current environment in which the application is running (this refers to the so-called "environment variables").
- What files are the program currently using?
Looking at the “snapshot” (they are often called “snapshots” - from snapshot) of the application state at any time, we can find out in what conditions the application worked at that moment, and, possibly, if necessary, recreate these conditions by giving application to the state in which it resided at the time of snapshot.
The state can be modified during the execution of the user certain actions. For example, if a user correctly moves a game character in a simple game, this may increase the number of points. In quite complex applications, the approach to state modification may become more complicated, state changes may come from different sources.
For example, in a multiplayer game, how many points the user scores depends not only on his actions, but also on the actions of those who play with him on the same team. And if a computer-controlled character successfully attacks the game character that the user controls, the user may lose some points.
Imagine that we are developing a front-end application like PWA Twitter. This is a one-page application that has several tabs, say - Home, Search (Search), Notifications, and Messages (Messages). Each such tab has its own working area, which is intended both for displaying certain information and for its modification. All this data forms the state of the application. So, new tweets, notifications and messages come into the app every few seconds. The user can work with the program and with this data. For example, he can create a tweet or delete it, he can retweet a certain tweet, he can read notifications, send messages to someone, and so on. Everything that was just discussed modifies the state of the application.
All of these tabs have their own set of user interface components used to display and modify data. The state of an application can be affected by data entering the application from the outside, and user actions.
It is clear that in such an application, sources of state changes can be different entities, and changes initiated by different sources can occur almost simultaneously. If we manage the state manually, it may turn out that it will be difficult for us to keep track of what is happening. These difficulties lead to contradictions. For example, a tweet may be deleted, but it will still be displayed in the tweet feed. Or, say, a user may read a notification or message, but it will still be displayed in the program as unvisited.
The user can like a tweet, a heart appears in the program's interface, but a network request that sends information about the like to the server will not work. As a result, what the user sees will be different from what is stored on the server. It is in order to prevent such situations, and you may need Redux.
How does redux work?
There are three basic concepts in the Redux library that are designed to make managing the state of applications simple and straightforward:
- Storage (store). Redux storage is a JavaScript object that represents the state of the application. It plays the role of "the only source of reliable data." This means that the entire application must rely on storage as the only entity responsible for the state representation.
- Actions The state store is read only. This means that it cannot be modified by referring to it directly. The only way to change the contents of the repository is to use actions. Any component that wants to change a state should use the appropriate action.
- Reducers (reducers), which are also called "converters." A reducer is a pure function that describes how a state is modified by actions. The reducer takes the current state and the action, the execution of which has been requested by a certain component of the application, and then returns the transformed state.
Using these three concepts means that the application should no longer directly monitor events that are sources of state changes (user actions, API responses, the occurrence of events related to receiving some data via the WebSocket protocol, and so on) and make decisions about how these events will affect the state.
Through the use of the Redux model, these events can trigger appropriate actions that will change state. Components that need to use data stored in the application state can simply subscribe to state changes and receive information of interest. Using all of these mechanisms, Redux aims to make changes in the state of an application predictable.
Here is a schematic example that demonstrates how you can organize a simple state management system using Redux in our fictional application:
import { createStore } from'redux';
// редьюсерconst tweets = (state = {tweets: []}, action) => {
switch (action.type) {
// Мы обрабатываем лишь одно действие, выполняемое при поступлении нового твита.
case'SHOW_NEW_TWEETS':
state.numberOfNewTweets = action.count;
return state.tweets.concat([action.tweets]);
default:
return state;
}
};
// Вспомогательная функция, с помощью которой создаётся действие. SHOW_NEW_TWEETSconst newTweetsAction = (tweets) => {
return {
type: 'SHOW_NEW_TWEETS',
tweets: tweets,
count: tweets.length
};
};
const store = createStore(tweets);
twitterApi.fetchTweets()
.then(response => {
// Вместо того, чтобы вручную добавлять твит в соответствующее место программы,
// мы отправляем действие Redux.
store.dispatch(newTweetsAction(response.data));
});
// Кроме того, мы используем действие SHOW_NEW_TWEETS когда пользователь создаёт твит// в результате твит пользователя тоже добавляется в состояние приложения.const postTweet = (text) => {
twitterApi.postTweet(text)
.then(response => {
store.dispatch(newTweetsAction([response.data]));
});
};
// Предположим, в приложение, по протоколу WebSocket, поступили новые твиты.// При возникновении этого события мы тоже можем отправить действие. SHOW_NEW_TWEETS
socket.on('newTweets', (tweets) => {
store.dispatch(newTweetsAction(tweets));
};
// Если мы используем некий фреймворк, вроде React, то наши компоненты нужно подключить к хранилищу,// нужно, чтобы они автоматически обновлялись бы для вывода новых твитов.// В противном случае нам нужно самостоятельно настроить прослушивание событий,// возникающих при изменении состояния.
store.subscribe(() => {
const { tweets } = store.getSTate();
render(tweets);
});
Taking this code as a basis, we can equip our state management system of the application with additional actions and send them from different places of the application without risking hopelessly confused.
Here is the material from which you can learn more about the three fundamental principles of Redux.
Now let's talk about using Redux in a server environment.
Transferring Redux principles to the server environment
We explored the Redux capabilities used in developing client applications. But, since Redux is a JavaScript library, it can theoretically be used in a server environment. Let us consider how the above principles can be applied on the server.
Remember how we talked about how the state of the client application looks like? It should be noted that there are some conceptual differences between client and server applications. Thus, client applications tend to maintain a state between various events, say, between the execution of server requests. Such applications are called stateful applications.
If they did not strive for state storage, then, for example, when working with a certain web service that requires entering a login and password, the user would have to perform this procedure every time he goes to a new page of the corresponding web interface.
Backend applications, on the other hand, tend to ensure that the state is not stored (they are also called stateless applications). Here, speaking of “backend applications”, we mainly refer to projects based on certain APIs that are separate from front-end applications. This means that the state of the system should be provided to similar applications each time they are accessed. For example, the API does not monitor whether the user is logged in or not. It determines its status by analyzing the authentication token in its requests to this API.
This leads us to an important reason why Redux would hardly be used on servers in the form in which we described its capabilities above.
The fact is that Redux was designed to store the temporary state of the application. But the state of the application stored on the server should usually be long enough. If you would use the Redux repository in your server-side Node.js application, then the state of this application would be cleared every time the process stops
node
. And if we are talking about a PHP server that implements a similar state management scheme, the state would be cleared when each new request arrives at the server.The situation becomes even more complicated if we consider server applications in terms of their scalability. If you had to scale the application horizontally, increasing the number of servers, then you would have a lot of Node.js processes running simultaneously, and each of them would have its own version of the state. This means that if two identical requests to the backend were received simultaneously, different answers could be given to them.
How to apply the principles of state management discussed on the server? Take another look at the Redux concepts and see how they are commonly used in a server environment:
- Storage. On the back end, “the only source of reliable data” is usually a kind of database. Sometimes, in order to facilitate access to data that is often required, or for some other reason, a copy of some part of this database can be made — as a cache or as a file. Usually such copies are read only. The mechanisms controlling them are subscribed to changes in the main repository and, if such changes occur, update the contents of the copies.
- Actions and rejusery. They are the only mechanisms used to change a state. In most backend applications, the code is written in an imperative style, which is not particularly conducive to the use of concepts of actions and reducers.
Consider two design patterns that, by their nature, are similar to what the Redux library is aimed at. This is CQRS and Event Sourcing. They, in fact, appeared before Redux, their implementation can be extremely difficult, so we will talk about them very briefly.
CQRS and Event Sourcing
CQRS (Command Query Responsibility Segregation, division of command and query responsibility) is a design pattern that, when implemented, an application reads data from the repository only with queries, and writes only with commands.
When using CQRS, the only way to change the state of an application is to send a command. Commands are similar to Redux actions. For example, in Redux, you can write code that corresponds to this scheme:
const action = { type: 'CREATE_NEW_USER', payload: ... };
store.dispatch(action);
// реализовать редьюсер для выполнения действияconst createUser = (state = {}, action) => {
//
};
When using CQRS, something like this will look like this:
// базовый класс или интерфейс командыclassCommand{
handle() {
}
}
classCreateUserCommandextendsCommand{
constructor(user) {
super();
this.user = user;
}
handle() {
// создать запись о пользователе в базе данных
}
}
const createUser = new CreateUserCommand(user);
// отправить команду (это вызовет метод handle())
dispatch(createUser);
// или здесь можно воспользоваться классом CommandHandler
commandHandler.handle(createUser);
Queries are the mechanisms for reading data in the CQRS template. They are equivalent to design
store.getState()
. In a simple implementation of CQRS, requests will interact directly with the database, getting records from it. The Event Sourcing template (event registration) is designed with a view to registering all changes in the application state as a sequence of events. This template is best suited for applications that need to know not only about their current state, but also about its history of changes, about how the application has reached its current state. As examples here you can cite the history of operations with bank accounts, tracking parcels, working with orders in online stores, organization of transportation, logistics.
Here is an example of implementing the Event Sourcing pattern:
// без использования шаблона Event SourcingfunctiontransferMoneyBetweenAccounts(amount, fromAccount, toAccount) {
BankAccount.where({ id: fromAccount.id })
.decrement({ amount });
BankAccount.where({ id: toAccount.id })
.increment({ amount });
}
functionmakeOnlinePayment(account, amount) {
BankAccount.where({ id: account.id })
.decrement({ amount });
}
// с использованием шаблона Event Sourcing
functiontransferMoneyBetweenAccounts(amount, fromAccount, toAccount) {
dispatchEvent(new TransferFrom(fromAccount, amount, toAccount));
dispatchEvent(new TransferTo(toAccount, amount, fromAccount));
}
functionmakeOnlinePayment(account, amount) {
dispatchEvent(new OnlinePaymentFrom(account, amount));
}
classTransferFromextendsEvent{
constructor(account, amount, toAccount) {
this.account = account;
this.amount = amount;
this.toAccount = toAccount;
}
handle() {
// сохранить новое событие OutwardTransfer в базе данных
OutwardTransfer.create({ from: this.account, to: this.toAccount, amount: this.amount, date: Date.now() });
// и обновить текущее состояние счёта
BankAccount.where({ id: this.account.id })
.decrement({ amount: this.amount });
}
}
classTransferToextendsEvent{
constructor(account, amount, fromAccount) {
this.account = account;
this.amount = amount;
this.fromAccount = fromAccount;
}
handle() {
// сохранить новое событие InwardTransfer в базе данных
InwardTransfer.create({ from: this.fromAccount, to: this.account, amount: this.amount, date: Date.now() });
// и обновить текущее состояние счёта
BankAccount.where({ id: this.account.id })
.increment({ amount: this.amount });
}
}
classOnlinePaymentFromextendsEvent{
constructor(account, amount) {
this.account = account;
this.amount = amount;
}
handle() {
// сохранить новое событие OnlinePayment в базе данных
OnlinePayment.create({ from: this.account, amount: this.amount, date: Date.now() });
// и обновить текущее состояние счёта
BankAccount.where({ id: this.account.id })
.decrement({ amount: this.amount });
}
}
What happens here also reminds us of working with Redux actions.
However, the event registration mechanism also organizes the long-term storage of information about each change in state, and not just the storage of the state itself. This allows us to reproduce these changes to the point in time we need, thus restoring the contents of the state of the application to this point in time. For example, if we need to understand how much money was in a bank account on a certain date, we only need to reproduce the events that occurred with the bank account until we get to the desired date. Events in this case are represented by receipts of funds to the account and debiting them from it, writing off a bank commission and other similar operations. In the event of errors (that is, in the event of events containing incorrect data), we can invalidate the current state of the application,
CQRS and Event Sourcing templates are often used together. And, interestingly, Redux is, in fact, partly based on these templates. Commands can be written so that when they are invoked, they send events. The events then interact with the repository (database) and update the state. In real-time applications, query objects can also listen for events and receive updated status information from the repository.
Using any of these templates in a simple application may unnecessarily complicate it. However, in the case of applications built to solve complex business problems, CQRS and Event Sourcing are powerful abstractions that help better model the subject area of such applications and improve their state management.
Note that the CQRS and Event Sourcing templates can be implemented differently, with some of their implementations being more complex than others. We have considered only very simple examples of their implementation. If you are writing server applications in Node.js, take a look at wolkenkit . This framework, among those that have been discovered in this area, provides the developer with one of the simplest interfaces for implementing CQRS and Event Sourcing templates.
Results
Redux is a great tool for managing application state, in order to make state changes predictable. In this article we talked about the key concepts of this library and found that, although using Redux in a server environment is probably not the best idea, similar principles can be applied to the server using the CQRS and Event Sourcing templates .
Dear readers! How do you organize the management of client and server applications?