Сервисно-ориентированный фронтенд: SOA в JavaScript

English version: Microservices in a frontend

В этом посте я рассмотрю подход к построению фронтенда веб-сайта с точки зрения парадигмы микросервисов и коммуникации между ними путём обмена событиями (сообщениями). Я рассмотрю это на примере фреймворка Backbone.js.

Почему Backbone? Потому что он из-коробки лучше всех (известных мне) фреймворков изначально предназначен для работы с данными – в отличие от других фреймворков, предназначенных для эффективной визуализации. Если вы понимаете, что Модели, их Отношения и Бизнес-логика – это главное, то вы со мной согласитесь. В то же время, существуют и другие точки зрения, и другие инструменты – более ориентированные на вёрстку, визуальные эффекты и взаимодействие блоков на странице с пользователем.

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

Основные компоненты Backbone:

1. Модель.
2. Коллекция (моделей).
3. Контроллер.
4. Шаблон.
5. Роутер.

Здесь имеется терминологическая разница: хотя Backbone относится к концепции MVC, то что фактически является контроллером – в нём называется View, а то что является представлением – называется Template. Не спрашивайте меня, почему. К этому просто нужно привыкнуть – и к счастью, это единственный недостаток.

Жизненный цикл Backbone:

1. Происходит изменение в модели или коллекции.
2. Контроллер получает уведомление об этом.
3. Производится обновление представления.

Это тот самый реактивный принцип, о котором вы часто слышите в разговорах про React. Здесь он тот же самый, и более того – если в реакте у вас данные лежат как попало (сырые), то в бэкбоне у вас строго определённые модели, как на бэкэнде. Коммуникация происходит благодаря встроенным средствам Backbone, и для разработчика выглядит как обычный publish-subscribe с событиями. Есть и более широкая система, реализующая паттерн Enterprise Service Bus, – называется Backbone Radio (Wreqr), которая по сути является общей шиной обмена сообщениями внутри и снаружи Backbone.

В обратную сторону это работает аналогично: если пользователь изменил что-то в представлении (ввёл текст в поле ввода), то контроллер реагирует на это как на событие, и производит изменения в модели. Можно автоматизировать этот процесс (two-ways data binding), а можно делать и вручную, если у вас нестандартная логика.

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

Однако задумайтесь, а что если:

1. Модель – это сервис.
2. Коллекция – это сервис.
3. Контроллер – это сервис.
4. Представление – это сервис.

Когда изменяется модель, она публикует своё новое состояние в рамках Backbone (согласно идеологии dataflow). Это сообщение (событие) может услышать и коллекция, и контроллер. В типичном случае, в этом случае контроллер слышит событие и репаблишит его представлению as is, чтобы то смогло перерисоваться в принудительном порядке.

Очень похоже на микросервисную архитектуру, основанную на событиях, не так ли?

Например, типичная задача фильтрации коллекции (выборки объектов, подходящих под условие) решается так: моделям добавляется булевый флаг – назовём его visible – и фильтрующая функция итеративно проходит коллекцию, проверяя каждую модель на соответствие. Если модель подходит – ей ставится visible:true, в ином случае – false. Если новое значение не отличается от предыдущего, то ничего более не происходит (строчка в таблице остаётся как есть – видима или не видима). А если значение изменилось – то строчка будет перерисована (отображена или скрыта). Это очень экономичный способ, и вы наверняка часто слышали об этом в React, где подобный алгоритм преподносится как исключительное know-how.

Однако есть и более интересные вещи: взаимодействие с бэкэндом. Когда модель меняет своё состояние, она может вызвать сохранение своего нового состояния на бэкэнде (persist). Технически это происходит путём отправки HTTP-запроса, однако мы можем сказать, что происходит публикация и отправка события – с новым состоянием модели, согласно идеологии dataflow.

С другой стороны, если вы строите бэкэнд по тем же принципам, когда на бэкэнде происходит обновление состояния модели – это также публикуется как событие-сообщение, в том числе отправляющееся на фронтенд (по SSE или WebSocket транспортам). Недавно я опубликовал рабочий пример как это делается.

two-ways realtime dataflow architecture (between frontend and backend)

Сообщение принимается на клиентской стороне, и публикуется в общую шину событий на основе Backbone Radio. Далее его может поймать, например, коллекция – и внедрить (merge) в себя, тем самым обновив состояние конкретной модели внутри Backbone. Ну а далее вы знаете: обновилась модель – перерисовалось представление.

Это отличная возможность построения социально-интерактивных веб-сайтов: список комментариев под постом, количество лайков, ответы на вопросы – всё обновляется “вживую”, благодаря фундаментальному механизму, без каких-либо кастомных контроллеров или методов.

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