Redux - отличный инструмент для управления состоянием в сложных интерфейсных приложениях. Можно ли распространить это на сервер?

Зачем мне нужен Redux?

Redux позиционирует себя на своей целевой странице как контейнер с предсказуемым состоянием для приложений JavaScript. Redux обычно описывается как инструмент управления состоянием, и хотя он в основном используется с React, его можно использовать везде, где используется JavaScript.

Мы сказали, что Redux предназначен для управления состоянием. Но что конкретно это за «состояние»? Состояние сложно определить, но давайте попробуем описать его. Когда мы говорим о состоянии людей или вещей, мы пытаемся описать их статус в данный момент времени, возможно, с помощью одного или нескольких параметров. Например, когда мы говорим «Озеро кипит» или «Озеро замерзло», мы описываем состояние озера с точки зрения температуры воды.

Когда вы говорите: «Я разорен», вы описываете свое состояние с точки зрения того, сколько у вас денег. Конечно, в каждом из этих примеров мы рассматриваем только один аспект состояния этих объектов. Вы можете добавить больше параметров, например, сказав: «Я разорился и еще не ел, но я счастлив!» Важно отметить, что состояние является временным. Это означает, что оно меняется, поэтому, когда мы запрашиваем состояние, мы понимаем, что через несколько секунд или минут оно может стать некорректным.

Что касается приложений, то «состояние» приобретает несколько нюансов. Во-первых, это данные, которые где-то хранятся. Обычно это находится в памяти (например, в объектах JavaScript), но также может быть в файлах, базе данных или механизме кеширования, таком как Redis. Во-вторых, состояние приложения обычно зависит от экземпляра. Поэтому, когда мы говорим о состоянии приложения, мы имеем в виду конкретный экземпляр, процесс или пользователя. Состояние приложения может включать в себя:

  • пользователь вошел в систему или нет? если да, то как долго пользователь находится в системе и когда истекает срок его сеанса?
  • (для игры) каков счет этого пользователя?
  • (для медиаплеера) с какой отметкой времени пользователь приостановил это видео?

На более низком уровне государство также может включать:

  • какие переменные установлены в текущей среде («переменные среды»)?
  • какие файлы сейчас используются программой?

Глядя на снимок состояния приложения в любой момент времени, мы можем получить представление об условиях, в которых приложение работало, и, возможно, воссоздать их.

Состояние может быть изменено действиями пользователя. Например, правильный ход может увеличить счет пользователя. В более сложных приложениях состояние может быть изменено из нескольких источников. Например, в многопользовательской игре счет пользователя также может быть увеличен действием его товарища по команде; получение удара от игрока, управляемого компьютером, могло привести к уменьшению счета.

Представим, что мы создаем интерфейсное приложение, такое как Twitter PWA (mobile.twitter.com). Это одностраничное приложение с вкладками для главной страницы, поиска, уведомлений и сообщений. Каждая вкладка представляет собой отдельное представление с входящими и исходящими данными. Все они составляют государство. Новые твиты, уведомления и сообщения транслируются в окно вашего браузера каждые пару секунд. Вы также можете взаимодействовать с ними - например, твитнуть, ретвитнуть твит, удалить твит, прочитать уведомление, отправить кому-то сообщение и так далее. Все эти действия изменяют состояние.

Теперь состояние может быть изменено из любого из множества источников, происходящих почти одновременно. Если вы управляете состоянием вручную, вы можете обнаружить, что становится трудно отслеживать, что происходит. Это приводит к несоответствиям: твит, возможно, был удален, но он все еще отображается на шкале времени. Или уведомление или сообщение могут быть прочитаны, но по-прежнему отображаться как непрочитанные. Пользователю может понравиться твит, поэтому значок сердца переключается немедленно, но фактический сетевой вызов не удался, поэтому теперь пользовательский интерфейс неверен. Вот почему вам может понадобиться Redux.

Совет: используйте Bit, чтобы быстрее создавать компоненты. Создайте коллекцию своих компонентов, поделитесь ею в команде, найдите применение и синхронизируйте их в своих приложениях. Создавайте быстрее и не пишите код дважды. Попробуйте.



Как работает Redux?

В Redux есть три основных концепции, которые призваны упростить управление состоянием.

  1. Магазин . Хранилище Redux - это объект JavaScript, который представляет состояние приложения. Он служит «единственным источником истины». Это означает, что все приложение должно полагаться на хранилище как на единственный орган, контролирующий состояние приложения.
  2. Действия. Состояние доступно только для чтения, поэтому его нельзя изменить напрямую. Действия - единственный способ обновить состояние. Любой компонент, который хочет изменить состояние, должен отправить соответствующее действие,
  3. Редукторы. Редюсер - это чистая функция, описывающая, как состояние изменяется действиями. Редуктор принимает текущее состояние и запрошенное действие и возвращает обновленное состояние.

Работа с этими тремя концепциями означает, что приложению больше не нужно напрямую прослушивать все входные данные (взаимодействия пользователей, ответы API, отправленные данные из WebSockets) и выяснять, как они изменяют состояние. В модели Redux эти входные данные могут запускать действия, которые обновят состояние. Необходимые компоненты приложения могут просто подписаться на состояние и отслеживать изменения, которые их касаются. Таким образом, Redux стремится сделать изменения состояния предсказуемыми. Милая, а?

Вот простой способ использования Redux в нашем вымышленном примере:

С этого момента мы можем добавлять больше действий и отправлять их из разных точек приложения, не сходя с ума, благодаря Redux. Вы можете узнать больше о концепциях Redux здесь:



Перевод этих принципов на сервер

Мы изучили, как Redux работает на стороне клиента. Поскольку Redux - это «просто JavaScript», теоретически его можно использовать на сервере. Давайте посмотрим, как можно применить эти концепции к серверу.

Помните, когда мы говорили о государстве? Что ж, с фронтендом дела обстоят несколько иначе. Внешний интерфейс стремится быть с отслеживанием состояния (то есть отслеживать состояние между запросами). Если интерфейс не отслеживал состояние, вам приходилось входить в систему каждый раз, когда вы переходили на новую страницу.

Бэкэнд, однако, стремится быть без сохранения состояния. (Примечание: когда мы говорим «бэкэнд», мы в первую очередь имеем в виду среды на основе API, которые отделены от внешнего приложения.) Это означает, что состояние должно предоставляться при каждом новом вызове. Например, API не отслеживает, вошли вы в систему или нет. Он определяет состояние вашей аутентификации, считывая токен в вашем запросе API.

Это подводит нас к основной причине, по которой вы не можете использовать Redux как есть на сервере: он был разработан для хранения переходного состояния. Но состояние приложения, хранящееся на бэкэнде, обычно должно быть долговечным. Если вы использовали хранилище Redux на своем сервере Node.js, состояние будет очищаться каждый раз, когда процесс node останавливается. На сервере PHP состояние будет очищаться при каждом запросе.

Это становится еще более сложным, если вы думаете о масштабировании. Если бы вы масштабировали свое приложение по горизонтали, добавляя больше серверов, у вас было бы несколько процессов Node, работающих одновременно, и каждый из них имел бы свою собственную версию состояния. Это означает, что два идентичных запроса к вашему бэкэнду в один и тот же момент могут легко получить два разных ответа.

Как же тогда мы можем применить эти принципы к серверной части? Давайте посмотрим на концепции и на то, как они часто применяются:

  • Магазин. На бэкэнде единственным источником истины часто является база данных. Иногда, чтобы упростить доступ к часто используемым данным или по какой-либо другой причине, это хранилище может быть дублировано частями в другие места, такие как кеш или файл. Обычно это копии, предназначенные только для чтения, и они отслеживают изменения в основном хранилище, чтобы оставаться в курсе.
  • Действия и редукторы: это единственные способы изменить состояние. В большинстве серверных приложений код написан императивным образом, что не соответствует концепциям действий и редукторов.

Давайте посмотрим на два шаблона проектирования, которые по своей природе похожи на то, что пытается делать Redux: CQRS и поиск событий. На самом деле они предшествуют Redux и могут быть очень сложными, поэтому мы рассмотрим их бегло.

CQRS и поиск событий

CQRS означает «Command-Query-Responsibility-Segregation». CQRS - это шаблон, в котором приложение читает из хранилища и записывает в него, используя две разные модели, запрос и команду соответственно.

В CQRS единственный способ изменить состояние - отправить команду. Команды похожи на действия в Redux. В Redux вы должны:

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

В системе CQRS это может быть:

Запросы - это то, как вы читаете состояние в CQRS. Это эквивалент store.getState(). В простой реализации ваш запрос будет напрямую обращаться к вашей базе данных и извлекать оттуда записи.

Источники событий предназначены для фиксации всех изменений состояния приложения в виде последовательности событий. Источники событий лучше всего подходят для приложений, которым необходимо знать не только текущее состояние, но и его историю - как оно было достигнуто. Примерами являются банковские счета, отслеживание посылок, заказы электронной торговли, транспортировка и логистика.

Упрощенный пример этого:

Это также похоже на действия Redux. Однако, в отличие от Redux, Event-sourcing также сохраняет эти изменения в долгосрочной перспективе. Это позволяет нам воспроизводить эти изменения до любого момента и воспроизводить состояние приложения в любой момент времени. Например, если нам нужно выяснить, сколько денег было на банковском счете в определенную дату, нам нужно только воспроизвести все события, которые произошли с учетной записью, пока мы не дойдем до этой даты. События в этом контексте будут включать внутренние переводы, исходящие переводы, банковские сборы, прямое дебетование и так далее. Когда происходят ошибки (события с неверными данными), мы можем отбросить текущее состояние приложения, исправить событие и заново вычислить состояние.

CQRS и поиск событий часто идут рука об руку. (Забавный факт: Redux фактически частично основан на CQRS и источнике событий.) Команды могут быть написаны так, чтобы они отправляли события при запуске. Затем события обращаются к хранилищу (базе данных) и обновляют состояние. В приложении реального времени объекты Query также могут прослушивать события и получать обновленное состояние из хранилища.

Использование любого из этих двух шаблонов в простых приложениях, вероятно, будет излишним и внесет ненужную сложность. Однако для приложений, созданных в сложной бизнес-области, они представляют собой мощные абстракции, которые лучше моделируют домен и управляют состоянием.

Обратите внимание, что CQRS и сбор событий могут быть реализованы по-разному, некоторые из них сложнее других. Пока что мы показали только простую реализацию в наших примерах. Если вы работаете с Node.js, советую вам взглянуть на Wolken Kit. Он предоставляет один из наиболее простых интерфейсов для реализации CQRS и поиска событий, который я обнаружил.

Заключение

Redux - отличный инструмент для управления состоянием и обеспечения предсказуемого поведения его мутаций. В этой статье мы рассмотрели его основные концепции и увидели, что, хотя использование Redux на сервере, вероятно, не является хорошей идеей, мы можем применить некоторые из его принципов к серверу. Чтобы узнать больше о CQRS и источниках событий, ознакомьтесь с этими статьями:





Учить больше