Государственное управление сходит с ума

Это вторая статья из серии, в которой мы попытаемся выяснить, есть ли место для Redux по ту сторону забора.

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

Корни

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

С помощью Redux распределение состояния в таком приложении будет выглядеть следующим образом.

Распределение состояния Redux во внешнем интерфейсе

Перемещение вещей

Почему

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

Сценарий

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

Представьте себе замену компонентов внешними интерфейсами. Интерфейсы, которые одновременно работают и взаимодействуют с одним и тем же состоянием. Это может быть одно и то же клиентское приложение в разных окнах браузера, а также совершенно разные клиентские приложения. Главный критерий: они должны взаимодействовать с одним и тем же государством.

На следующей диаграмме представлен концептуальный граф распределения состояний для такого приложения. Левая и правая стороны — это отдельные интерфейсы React с сервером, оснащенным Redux, посередине. Один из компонентов правого интерфейса выполняет изменение состояния (зеленый кружок), изменение (действие), доставляемое на сервер, откуда оно отправляется в Redux Store. Сокращающая функция выполняет все необходимые изменения состояния, и, наконец, новое состояние доставляется обратно во внешние интерфейсы. В этом случае мы используем модель распределения компонентов верхнего уровня в обоих интерфейсах для передачи состояния зависимым компонентам (желтые кружки).

Распределение состояния Back-end Redux

Как

Пока все хорошо, это имеет смысл, и все кажется логически согласованным. Однако неясным моментом здесь является обмен состояниями.
В обычном приложении React все происходит внутри браузера в одной и той же среде выполнения JavaScript, что обеспечивает двустороннюю связь между Redux и React в реальном времени. Перемещение Redux на серверную часть вводит физическое разделение между корневым компонентом React и Redux Store. Черная пунктирная линия на приведенной выше диаграмме иллюстрирует физическое разделение этих двух компонентов. Чтобы заставить Redux работать так, как мы ожидаем, мы должны сделать общение таким же бесшовным, как и в его естественной среде обитания.

Соревнование

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

Нам нужно установить двустороннюю связь в режиме реального времени между Redux Store и корневым узлом каждого внешнего интерфейса React. Это означает, что и клиент, и сервер должны иметь возможность передавать информацию в равной степени.

HTTP против веб-сокета

Эта тема сама по себе заслуживает отдельной статьи. Чтобы сэкономить время и не потерять фокус, скажу, что HTTP из коробки поддерживает подход server-push с Server-Sent Events (SSE), а клиентский JS имеет встроенную поддержку для этого, благодаря HTML5. Кроме того, HTTP/2 может использовать одно TCP-соединение для доставки нескольких сообщений в обоих направлениях, что делает его полнодуплексным двунаправленным соединением.

Однако в дальнейшем я выбрал WebSocket в качестве протокола, специально созданного для такого рода связи, он не создает ненужных накладных расходов на данные, которые приносит HTTP (например, заголовки). Кроме того, WebSocket — более известный способ решения такого рода задач.

Теперь осталось только соединить правильные точки правильными линиями.

Клиент

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

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

Сервер

Чтобы использовать события «push» на клиенте, нам сначала нужно их создать. Каждый раз, когда редуцирующая функция создает новое состояние, сервер должен инициировать событие «push». И, наконец, нам нужно обрабатывать входящие действия от клиента.

Для отправки состояния мы можем использовать обратные вызовы прослушивателя Redux, которые будут выполняться при каждой попытке изменить состояние, независимо от того, было ли оно изменено или нет. В любой момент времени мы можем запросить новое состояние и использовать WebSocket для доставки его клиенту.

Действия процесса предельно просты. Как только мы получаем действие, мы напрямую отправляем его в Redux Store.

Окончательный дизайн

Это оно. У нас все на своих местах, у нас есть способ доставки действий в Redux Store, а также механизм подписки для обновления всех интерфейсов при каждом изменении состояния.

Окончательный дизайн выглядит следующим образом

Что дальше?

Оптимизация

Вы можете подумать, что отправка состояния каждый раз всем клиентам — не самый эффективный подход. И вы правы. Но разве это проблема? Так это или нет, это действительно зависит от того, насколько велико ваше дерево состояний. Если бы он был достаточно маленьким, я бы не стал заморачиваться. Если он достаточно большой и вас беспокоит задержка некоторых клиентов, вы можете уменьшить шум данных, например. отправляя только дельту состояния.

Redux везде

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

Источник истинного состояния по-прежнему остается на сервере, но взаимодействие клиент-сервер, происходящее между Redux на стороне сервера и Redux на стороне клиента, и теперь распространение состояния на стороне клиента ложится на плечи Redux на стороне клиента.

Вывод

Redux на бэкенде — это не волшебный единорог, не просто теория и мы докажем это на практике.

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

Он абсолютно независим от сложности внешнего интерфейса и может хорошо работать с простыми приложениями с водопадом состояний, а также со сложными внешними интерфейсами с Redux в качестве менеджера состояний.

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

Первоначально опубликовано на https://valerii-udodov.com 4 апреля 2020 г.