¿Puedo usar Redux en un servidor?

Redux es una excelente herramienta para administrar el estado de aplicaciones front-end complejas. El autor del material, cuya traducción publicamos hoy, encontrará la respuesta a la pregunta de si es posible aprovechar las características de Redux en el entorno del servidor.
imagen

¿Por qué necesito una biblioteca Redux?


La página de inicio de la biblioteca Redux dice que es un "contenedor de estado predecible para aplicaciones JavaScript". Por lo general, se hace referencia a Redux como una herramienta para administrar el estado de una aplicación, y aunque esta biblioteca se usa principalmente con React, se puede usar en cualquier proyecto basado en JavaScript.

Ya hemos mencionado que Redux se usa para controlar el estado de una aplicación. Ahora hablemos de lo que es una "condición". Este concepto es bastante difícil de definir, pero aún tratamos de describirlo.

Teniendo en cuenta el "estado", si estamos hablando de personas o de los objetos del mundo material, nos esforzamos por describir, de hecho, su estado en el momento en el que estamos hablando de ellos, posiblemente considerando uno o más parámetros. Por ejemplo, podemos decir sobre el lago: "El agua está muy caliente" o: "El agua está congelada". En estas declaraciones, describimos el estado del lago en términos de la temperatura del agua.

Cuando alguien dice acerca de sí mismo: "Estoy encallado", considera la cantidad de dinero que tiene. Está claro que en cada uno de estos ejemplos estamos hablando solo de un aspecto del estado de los objetos. Pero, en el ejemplo sobre el dinero, la declaración puede describir varios parámetros: "Estoy encallado, no he comido en mucho tiempo, ¡pero estoy feliz!" Es muy importante señalar aquí que el estado es algo impermanente. Esto significa que puede cambiar. Por lo tanto, cuando nos enteramos del estado actual de un determinado objeto, entendemos que su estado real puede cambiar unos segundos o minutos después de conocerlo.

Cuando tratamos con programas, algunas características están asociadas con el concepto de "estado". En primer lugar, el estado de la aplicación está representado por datos almacenados en algún lugar. Por ejemplo, estos datos pueden almacenarse en la memoria (por ejemplo, como un objeto JavaScript), pero pueden almacenarse en un archivo, en una base de datos y utilizando algún mecanismo de almacenamiento en caché como Redis. En segundo lugar, el estado de una aplicación generalmente está vinculado a su instancia específica. Por lo tanto, cuando hablamos sobre el estado de la aplicación, nos referimos a una instancia específica de esta aplicación, un proceso, un entorno de trabajo organizado en la aplicación para un usuario específico. El estado de una aplicación puede incluir, por ejemplo, la siguiente información:

  • ¿El usuario ha iniciado sesión o no? Si es así, ¿cuánto dura la sesión y cuándo caducará?
  • ¿Cuántos puntos obtuvo el usuario? Tal pregunta es relevante, por ejemplo, para cierto juego.
  • ¿Dónde detuvo exactamente el usuario el video? Esta pregunta puede hacerse sobre la aplicación del reproductor de video.

Si hablamos del estado de las aplicaciones en un nivel inferior, puede incluir, por ejemplo, la siguiente información:

  • Qué variables se establecen en el entorno actual en el que se ejecuta la aplicación (esto se refiere a las llamadas "variables de entorno").
  • ¿Qué archivos está usando el programa actualmente?

Al observar la "instantánea" (a menudo se les llama "instantáneas" - de instantáneas) del estado de la aplicación en cualquier momento, podemos averiguar en qué condiciones la aplicación funcionó en ese momento y, si es necesario, recrear estas condiciones mediante aplicación al estado en el que se encontraba al momento de recibir la instantánea.

El estado puede modificarse durante la ejecución de ciertas acciones por parte del usuario. Por ejemplo, si el usuario mueve correctamente el personaje del juego en un juego simple, esto puede aumentar el número de puntos. En aplicaciones bastante complejas, el enfoque para modificar un estado puede volverse más complicado; los cambios de estado pueden provenir de diferentes fuentes.

Por ejemplo, en un juego multijugador, cuántos puntos obtiene un usuario depende no solo de sus acciones, sino también de las acciones de aquellos que juegan con él en el mismo equipo. Y si un personaje controlado por computadora ataca con éxito un personaje del juego que controla el usuario, el usuario puede perder una cierta cantidad de puntos.

Imagine que estamos desarrollando una aplicación front-end como PWA Twitter . Esta es una aplicación de una página en la que hay varias pestañas, por ejemplo: Inicio, Búsqueda, Notificaciones y Mensajes. Cada pestaña tiene su propio espacio de trabajo, que está destinado tanto para mostrar cierta información como para su modificación. Todos estos datos forman el estado de la aplicación. Entonces, nuevos tweets, notificaciones y mensajes llegan a la aplicación cada pocos segundos. El usuario puede trabajar con el programa y con estos datos. Por ejemplo, puede crear un tweet o eliminarlo, puede retuitear un tweet, puede leer notificaciones, enviar mensajes a alguien, etc. Todo lo que se acaba de discutir modifica el estado de la aplicación.


Todas estas pestañas tienen su propio conjunto de componentes de interfaz de usuario que se utilizan para mostrar y modificar datos. El estado de la aplicación puede verse afectado por los datos que ingresan a la aplicación desde el exterior y las acciones del usuario

Está claro que en dicha aplicación, las fuentes de los cambios de estado pueden ser entidades diferentes, mientras que los cambios iniciados por diferentes fuentes pueden ocurrir casi simultáneamente. Si manejamos el estado manualmente, puede resultarnos difícil monitorear lo que está sucediendo. Estas dificultades conducen a contradicciones. Por ejemplo, un tweet puede eliminarse, pero aún se mostrará en la secuencia de tweets. O, digamos, el usuario puede leer la notificación o el mensaje, pero aún se mostrará en el programa como no visto.

Al usuario le puede gustar el tweet, aparecerá un corazón en la interfaz del programa, pero una solicitud de red que envía información similar al servidor no funcionará. Como resultado, lo que ve el usuario será diferente de lo que está almacenado en el servidor. Con el fin de prevenir tales situaciones, puede ser necesario Redux.

¿Cómo funciona Redux?


En la biblioteca Redux, hay tres conceptos principales que tienen como objetivo hacer que la gestión del estado de las aplicaciones sea simple y directa:

  1. Almacenamiento (tienda). Un repositorio de Redux es un objeto de JavaScript que representa el estado de una aplicación. Desempeña el papel de "la única fuente de datos confiables". Esto significa que toda la aplicación debe confiar en el almacenamiento como la única entidad responsable de representar al estado.
  2. Acciones La tienda estatal es de solo lectura. Esto significa que no se puede modificar accediendo directamente a él. La única forma de modificar el contenido del repositorio es usar acciones. Cualquier componente que quiera cambiar de estado debe tomar las medidas apropiadas.
  3. Reductores (reductores), que también se denominan "convertidores". Un reductor es una función pura que describe cómo se modifica un estado a través de acciones. El reductor toma el estado y la acción actuales, cuya ejecución fue solicitada por un determinado componente de la aplicación, después de lo cual devuelve el estado transformado.

El uso de estos tres conceptos significa que la aplicación ya no debería monitorear directamente los eventos que son fuentes de cambios de estado (acciones del usuario, respuestas API, la ocurrencia de eventos asociados con la recepción de ciertos datos a través del protocolo WebSocket, etc.) y tomar decisiones sobre cómo Estos eventos afectarán la condición.

Al usar el modelo Redux, estos eventos pueden desencadenar acciones que cambiarán de estado. Los componentes que necesitan usar datos almacenados en el estado de la aplicación simplemente pueden suscribirse a los cambios de estado y recibir información de interés para ellos. Al utilizar todos estos mecanismos, Redux se compromete a realizar cambios predecibles en el estado de la aplicación.

Aquí hay un ejemplo esquemático que demuestra cómo puede organizar un sistema de administración de estado simple usando Redux en nuestra aplicación ficticia:

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_TWEETS const 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); }); 

Tomando este código como base, podemos equipar nuestro sistema de gestión del estado de la aplicación con acciones adicionales y enviarlas desde diferentes lugares de la aplicación sin correr el riesgo de confundirse irremediablemente.

Aquí está el material del cual puede aprender más sobre los tres principios fundamentales de Redux.

Ahora hablemos sobre el uso de Redux en un entorno de servidor.

Migración de los principios de Redux al entorno del servidor


Exploramos las características de Redux utilizadas en el desarrollo de aplicaciones cliente. Pero, dado que Redux es una biblioteca de JavaScript, teóricamente también se puede usar en un entorno de servidor. Reflexionaremos sobre cómo se pueden aplicar los principios anteriores en el servidor.

¿Recuerdas cómo hablamos sobre el estado de la aplicación cliente? Cabe señalar que existen algunas diferencias conceptuales entre las aplicaciones cliente y servidor. Por lo tanto, las aplicaciones cliente tienden a mantener el estado entre varios eventos, por ejemplo, entre la ejecución de solicitudes al servidor. Dichas aplicaciones se denominan aplicaciones con estado.

Si no se esforzaron por almacenar el estado, entonces, por ejemplo, al trabajar con un determinado servicio web que requiere un nombre de usuario y contraseña, el usuario tendría que realizar este procedimiento cada vez que vaya a una nueva página de la interfaz web correspondiente.

Las aplicaciones de fondo, por otro lado, se esfuerzan por no almacenar el estado (también se denominan aplicaciones sin estado). Aquí, hablando de "aplicaciones de back-end", nos referimos principalmente a proyectos basados ​​en ciertas API que son independientes de las aplicaciones front-end. Esto significa que la información sobre el estado del sistema debe proporcionarse a aplicaciones similares cada vez que se accede a ellas. Por ejemplo, la API no supervisa si el usuario ha iniciado sesión o no. Determina su estado analizando el token de autenticación en sus solicitudes a esta API.

Esto nos lleva a una razón importante por la que Redux difícilmente se usaría en servidores en la forma en que describimos sus capacidades anteriormente.

El hecho es que Redux fue diseñado para almacenar el estado temporal de la aplicación. Pero el estado de la aplicación almacenada en el servidor generalmente debería existir el tiempo suficiente. Si usaría el repositorio de Redux en su aplicación Node.js del lado del servidor, el estado de esta aplicación se eliminaría cada vez node se detenga el proceso del node . Y si estamos hablando de un servidor PHP que implementa un esquema de administración de estado similar, entonces el estado se borrará cuando cada nueva solicitud llegue al servidor.

La situación es aún más complicada si consideramos las aplicaciones de servidor en términos de su escalabilidad. Si tuviera que escalar la aplicación horizontalmente, aumentando el número de servidores, entonces tendría muchos procesos Node.js ejecutándose al mismo tiempo, y cada uno de ellos tendría su propia opción de estado. Esto significa que con la recepción simultánea de dos solicitudes idénticas al backend, bien podrían haberse dado diferentes respuestas.

¿Cómo aplicar los principios de gestión del estado discutidos por nosotros en el servidor? Echemos otro vistazo a los conceptos de Redux y veamos cómo se usan generalmente en un entorno de servidor:

  1. Repositorio. En el backend, "la única fuente de datos confiables" suele ser una base de datos. A veces, para facilitar el acceso a los datos que a menudo se requieren, o por alguna otra razón, se puede hacer una copia de alguna parte de esta base de datos, en forma de caché o en forma de archivo. Por lo general, tales copias son de solo lectura. Los mecanismos que los controlan están suscritos a los cambios en el repositorio principal y, cuando se producen dichos cambios, actualizan el contenido de las copias.
  2. Acciones y reductores. Son los únicos mecanismos utilizados para cambiar de estado. En la mayoría de las aplicaciones de back-end, el código está escrito en un estilo imperativo, que no es particularmente propicio para el uso de conceptos de acción y reductores.

Considere dos patrones de diseño que, por su naturaleza, son similares a lo que pretende hacer la biblioteca Redux. Estos son CQRS y Event Sourcing. De hecho, aparecieron antes de Redux, su implementación puede ser extremadamente difícil, por lo que hablaremos de ellos muy brevemente.

CQRS y abastecimiento de eventos


CQRS (segmentación de responsabilidad de consulta de comando) es un patrón de diseño, en cuya implementación la aplicación lee datos de la tienda solo usando consultas y escribiendo solo usando comandos.

Cuando se usa CQRS, la única forma de cambiar el estado de una aplicación es enviar un comando. Los comandos son similares a las acciones de Redux. Por ejemplo, en Redux, puede escribir código que coincida con este esquema:

 const action = { type: 'CREATE_NEW_USER', payload: ... }; store.dispatch(action); //      const createUser = (state = {}, action) => { // }; 

Al usar CQRS, algo como esto se vería así:

 //      class Command { handle() { } } class CreateUserCommand extends Command { constructor(user) {   super();   this.user = user; } handle() {   //        } } const createUser = new CreateUserCommand(user); //   (   handle()) dispatch(createUser); //      CommandHandler commandHandler.handle(createUser); 

Las consultas son mecanismos de lectura de datos en la plantilla CQRS. Son equivalentes a la construcción store.getState() . En una implementación simple de CQRS, las consultas interactuarán directamente con la base de datos, recuperando registros de ella.

La plantilla de abastecimiento de eventos está diseñada para registrar todos los cambios en el estado de la aplicación como una secuencia de eventos. Esta plantilla es más adecuada para aplicaciones que necesitan saber no solo sobre su estado actual, sino también sobre el historial de sus cambios, sobre cómo la aplicación alcanzó su estado actual. Como ejemplos aquí, puede citar el historial de operaciones con cuentas bancarias, rastrear paquetes, trabajar con pedidos en tiendas en línea, la organización del transporte de carga, la logística.

Aquí hay un ejemplo de implementación de la plantilla de Abastecimiento de eventos:

 //    Event Sourcing function transferMoneyBetweenAccounts(amount, fromAccount, toAccount) {   BankAccount.where({ id: fromAccount.id })     .decrement({ amount });   BankAccount.where({ id: toAccount.id })     .increment({ amount }); } function makeOnlinePayment(account, amount) {   BankAccount.where({ id: account.id })     .decrement({ amount }); } //    Event Sourcing function transferMoneyBetweenAccounts(amount, fromAccount, toAccount) {   dispatchEvent(new TransferFrom(fromAccount, amount, toAccount));   dispatchEvent(new TransferTo(toAccount, amount, fromAccount)); } function makeOnlinePayment(account, amount) {   dispatchEvent(new OnlinePaymentFrom(account, amount)); } class TransferFrom extends Event {   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 });   } } class TransferTo extends Event {   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 });   } } class OnlinePaymentFrom extends Event {   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 });   } } 

Lo que sucede aquí también se parece a trabajar con acciones de Redux.

Sin embargo, el mecanismo de registro de eventos también organiza el almacenamiento a largo plazo de información sobre cada cambio de estado, y no solo el almacenamiento del estado en sí. Esto nos permite reproducir estos cambios en el momento que necesitamos, restaurando así el contenido del estado de la aplicación en este momento. Por ejemplo, si necesitamos comprender cuánto dinero había en la cuenta bancaria para una fecha determinada, solo necesitamos reproducir los eventos que ocurrieron con la cuenta bancaria hasta llegar a la fecha correcta. Los eventos en este caso están representados por recibos de fondos en la cuenta y debitándolos de ella, debitando la comisión bancaria y otras operaciones similares. Si se producen errores (es decir, cuando ocurren eventos que contienen datos incorrectos), podemos invalidar el estado actual de la aplicación, corregir los datos correspondientes y volver al estado actual de la aplicación, ahora formado sin errores.

Las plantillas CQRS y Event Sourcing a menudo se usan juntas. Y, curiosamente, Redux, de hecho, se basa en parte en estas plantillas. Los comandos se pueden escribir para que, cuando se los llame, envíen eventos. Luego, los eventos interactúan con el repositorio (base de datos) y actualizan el estado. En aplicaciones en tiempo real, los objetos de consulta también pueden escuchar eventos y recibir información de estado actualizada del repositorio.

El uso de cualquiera de estas plantillas en una aplicación simple puede complicarlo innecesariamente. Sin embargo, en el caso de las aplicaciones creadas para resolver problemas comerciales complejos, CQRS y Event Sourcing son abstracciones poderosas que ayudan a modelar mejor el área temática de dichas aplicaciones y mejorar su gestión estatal.

Tenga en cuenta que los patrones CQRS y Event Sourcing se pueden implementar de diferentes maneras, mientras que algunas de sus implementaciones son más complejas que otras. Consideramos solo ejemplos muy simples de su implementación. Si está escribiendo aplicaciones de servidor en Node.js, eche un vistazo a wolkenkit . Este marco, entre lo que se encontró en esta área, proporciona al desarrollador una de las interfaces más simples para implementar las plantillas de CQRS y Event Sourcing.

Resumen


Redux es una gran herramienta para administrar el estado de una aplicación, a fin de hacer que los cambios de estado sean predecibles. En este artículo, hablamos sobre los conceptos clave de esta biblioteca y descubrimos que, aunque el uso de Redux en un entorno de servidor probablemente no sea una buena idea, puede aplicar principios similares en un servidor usando las plantillas CQRS y Event Sourcing .

Estimados lectores! ¿Cómo se organiza la gestión del estado de las aplicaciones cliente y servidor?

Source: https://habr.com/ru/post/437804/


All Articles