Как создать веб-приложение на основе веб-сокетов в реальном времени?

17

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

Прежде чем углубляться в детали, просто немного контекста:

  • Веб-приложение представляет собой SPA в реальном времени;
  • Серверная часть находится в Ruby on Rails. События в реальном времени помещаются Ruby в ключ Redis, затем сервер микроузлов возвращает его и отправляет его в Socket.Io;
  • Внешний интерфейс находится в AngularJS и подключается непосредственно к серверу socket.io в Node.

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

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

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

  • Как структурировать данные, которые отправляются с сервера пользователю?
    • Должен ли я отправлять только события типа «этот ресурс обновлен, и вам следует перезагрузить его с помощью вызова AJAX» или отправить обновленные данные и заменить предыдущие данные, загруженные с помощью первоначальных вызовов AJAX?
    • Как определить связный и масштабируемый каркас для отправляемых данных? это сообщение об обновлении модели или сообщение об ошибке "blahblahblah"
  • Как не отправлять данные обо всем откуда угодно в бэкэнд?
  • Как уменьшить дублирование бизнес-логики как на стороне сервера, так и на стороне клиента?
Филипп Дурикс
источник
Вы уверены, что Rails - лучший вариант для вашего SPA? Rails хорош, но он нацелен на монолитное приложение ... вам может потребоваться модульный бэкэнд с разделением задач ... В зависимости от ваших потребностей, я бы рассмотрел альтернативные Ruby-фреймворки для SPA в реальном времени.
Мист
1
Я не уверен, что Rails - лучший вариант, но я очень доволен стеком, установленным, по крайней мере, на бэкэнде (вероятно, потому, что я хорош в этой конкретной среде). Здесь я больше беспокоюсь о том, как спроектировать SPA с технологической точки зрения. И я также не хочу увеличивать количество языков, фреймворков и библиотек в одном проекте.
Филипп Дурикс
Связанный тест может иметь проблемы, но он демонстрирует слабость в текущей реализации ActiveRecord. Я могу быть предвзятым в отношении plezi.io , но, как указывалось в более поздних результатах теста , он обеспечивает значительное улучшение производительности даже до кластеризации и Redis (которые не были протестированы). Я думаю, что он работал лучше, чем node.js ... Пока все не изменится, я бы использовал plezi.io.
Myst

Ответы:

10

Как структурировать данные, которые отправляются с сервера пользователю?

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

Например, если вы обновляли биржевую биржу, а AAPL изменились, вам не хотелось бы сбрасывать все цены на акции или даже все данные об AAPL (имя, описание и т. Д.). Вы только подтолкнули бы AAPL, дельту и новую цену. На клиенте вы затем обновите только ту цену акций в представлении.

Должен ли я отправлять только события типа «этот ресурс обновлен, и вам следует перезагрузить его с помощью вызова AJAX» или отправить обновленные данные и заменить предыдущие данные, загруженные с помощью первоначальных вызовов AJAX?

Я бы сказал, нет. Если вы отправляете событие, продолжайте и отправьте с ним соответствующие данные (а не данные всего объекта). Дайте ему имя для такого рода событий. (Наименование и данные, относящиеся к этому событию, выходят за рамки механической работы системы. Это больше связано с моделированием бизнес-логики.) Ваши средства обновления представления должны знать, как преобразовать каждое конкретное событие в точное изменение вида (т.е. только обновление того, что изменилось).

Как определить связный и масштабируемый каркас для отправляемых данных? это сообщение об обновлении модели или сообщение об ошибке "blahblahblah"

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

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

Как не отправлять данные обо всем откуда угодно в бэкэнд?

Используйте шаблон публикации / подписки . Когда ваш SPA загружает новую страницу, которая заинтересована в получении обновлений в реальном времени, страница должна подписываться только на те события, которые она может использовать, и вызывать логику обновления представления по мере поступления этих событий. Возможно, вам понадобится логика pub / sub на сервер для снижения нагрузки на сеть. Существуют библиотеки для публикации / публикации Websocket, но я не уверен, что это такое в экосистеме Rails.

Как уменьшить дублирование бизнес-логики как на стороне сервера, так и на стороне клиента?

Похоже, вам нужно обновить данные представления на клиенте и сервере. Я предполагаю, что вам нужны данные вида на стороне сервера, чтобы у вас был моментальный снимок, чтобы запустить клиент в реальном времени. Поскольку задействованы два языка / платформы (Ruby и Javascript), логика обновления представления должна быть записана на обоих языках. Помимо транспаранта (у которого есть свои проблемы), я не вижу способа обойти это.

Технический момент: манипулирование данными (просмотр обновлений) не является бизнес-логикой. Если вы имеете в виду проверку варианта использования, то это кажется неизбежным, поскольку проверки клиента необходимы для хорошего пользовательского опыта, но в конечном итоге не могут быть доверены серверу.


Вот как я вижу такую ​​вещь, хорошо структурированную.

Просмотры клиента:

  • Запрашивает снимок представления и последний увиденный номер события представления
    • Это позволит предварительно заполнить представление, чтобы клиенту не приходилось строить с нуля.
    • Может быть через HTTP GET для простоты
  • Создает соединение через веб-сокет и подписывается на определенные события, начиная с номера последнего события представления.
  • Получает события через веб-сокет и обновляет его представление в зависимости от типа события / данных.

Клиентские команды:

  • Запросить изменение данных (HTTP PUT / POST / DELETE)
    • Ответ только успех или неудача + ошибка
    • (События, сгенерированные изменением, будут проходить через веб-сокет и запускать обновление представления.)

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

Я описал форму CQRS + Messaging и типичную стратегию для решения проблем, с которыми вы сталкиваетесь.

Я не включил Event Sourcing в это описание, так как не уверен, что это то, что вы хотите взять на себя, или вам это нужно обязательно. Но это связанный шаблон.

Кейси Спикман
источник
Я много продвинулся в этой теме, и указатели, которые вы дали, были очень полезны. Я принял ответ, потому что я использовал многие из советов, даже если я не использовал все это. Я собираюсь описать путь, которым я следовал, в другом ответе.
Филипп Дурикс
4

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

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

После того, как все было организовано в ресурсы, я смог прикрепить сообщения в реальном времени к моделям.

  • Создание запускает сообщение с отверстием нового ресурса;
  • Обновление запускает сообщение только с обновленными атрибутами (плюс UUID);
  • Удаление запускает сообщение об удалении.

В Rest API все методы create, update, delete генерируют ответ только заголовок, HTTP-код информирует об успехе или неудаче и о фактических данных, передаваемых через веб-сокеты.

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


Мне показалось, что чтение CQRS + Messaging и Event Sourcing очень интересно, но мне показалось, что оно несколько усложнило мою проблему и, возможно, более приспособлено к интенсивным приложениям, где фиксация данных в централизованной базе данных - дорогая роскошь. Но я обязательно учту такой подход.

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

Филипп Дурикс
источник