Привет, Habr! Я frontend-разработчик в Альфа-Банк. В статье я хотел бы поделиться опытом внедрения мобильной версии web приложения «Альфа-Бизнес» в нашу архитектуру. А точнее — как без массовых доработок 100 фронтовых сервисов мы заставили работать mobile web версию с новой навигацией.
Примечание. «Альба-Бизнес» — веб приложение для юридических лиц.
Почему отдельная версия?
В связи с блокировкой приложений банка в AppStore и GooglePlay было принято решение разработать мобильное PWA (Progressive Web Application), которое можно легко установить, создав ярлык на рабочем экране телефона.
iOS устройства имеют существенные ограничения по установке приложений, Android таких ограничений не имеет, мы можем без особых сложностей установить приложение, скачав его с официального сайта.
На данный момент наше приложение имеет адаптивный режим отображения, но в некоторых разделах банка пользовательский опыт мобильных приложений существенно отличается от текущего.
Основная концепция mobile web заключается в повторении UX/UI нативных приложений, чтобы пользователи не чувствовали сильной разницы при работе с мобильного устройства без установленного приложения. В некоторых случаях нам пришлось бы создавать новый интерфейс с нуля и мы решили пойти в сторону гибридной разработки, когда продуктовые команды сами определяют необходима ли mobile web версия их продукта или можно обойтись адаптивной версией.
Архитектура
Перед тем как рассказать о подходах, которые мы реализовали, стоит рассказать как устроены наши приложения.
На момент написания их более 100 штук. Каждое фронтовое приложение включает в себя nginx слой, BFF (Backend For Frontend) на Node.JS, который содержит SSR (server side rendering) и клиентскую часть, написанную с использованием React. Все приложения связаны одним общим приложением shared
, который поставляет общие элементы (menu, header, footer). Важный элемент этого приложения — навигация.
Приложение shared
немного выходит из общей концепции приложений, так как не имеет собственной страницы, это микрофронт, который используют все приложения. Приложения запрашивают ресурсы и используют их для подготовки страницы: рисуют меню, подписываются на события и тд.
Выглядит это следующим образом:
Где подвох?
Чтобы обычное приложение могло взаимодействовать с приложением shared
, ему необходимо запросить ресурсы. У нас это метод getResources
. И это прекрасно работает, но контракт не предполагает передачу userAgent
, по которому мы могли бы определить какой устройство запрашивает ресурсы.
И может возникнуть мысль:
Обычная история, обновление контракта.
Это так, но у нас больше 100 фронтовых сервисов, в которых необходимо было обновить взаимодействие, чтобы заработала mobile web версия приложения shared
. Это слишком дорого. Нам необходимо было реализовать решение, которое работало бы без обновления и чтобы приложения получали мобильную навигацию. В конечном итоге обновление произойдет, но не сразу.
Тут наступает самое интересное...
Обновление метода getResources
Этап 1-й
В первую очередь мы пошли в сторону целевого решения, когда у нас есть информация о клиенте. Здесь все довольно просто:
Обновили метод загрузки ресурсов, используемый в каждом приложении.
type GetResourcesParams = { ..., userAgent: string; // Добавление нового обязательного поля } function getResources(params: GetResourcesParams) {...};
Определяем устройства пользователя, используя информацию
userAgent.
Отдаём соответствующие ресурсы.
Этап 2-й
На втором шаге итерации реализовали механизм когда информации о клиенте нет. Но это не совсем так… Информации нет при серверном взаимодействии между приложениями:
Но есть этап загрузки статичных файлов (JS, CSS) на клиенте:
Так как в каждом запросе от клиента передается userAgent
, этим мы и воспользовались!
Пришлось реализовать отдельный endpoint для обработки таких запросов, потому что раздачей статики у нас занимается nginx
и он не пропускает запросы на наш BFF.
Начнем с взаимодействия BFF application -> BFF shared application
:
В приложении
shared
определили новый endpoint/bundle.js
, который отвечает за запросы нашей статики.Возвращаем этот endpoint вместо статики по умолчанию.
BFF application
запрашивает статику.BFF shared application
видит, чтоuserAgent
отсутствует.Отдаёт соответствующие ресурсы.
Работает это следующим образом:
Перейдем к взаимодействию Client -> BFF shared application.
Клиент запрашивает ресурсы
/bundle.js.
BFF shared application
определяет наличие заголовкаuserAgent.
Отдаёт соответствующие ресурсы.
Во время гидратации React определяет различия между серверным и клиентским рендерингом и делает выбор в пользу клиентского рендеринга.
УРА! Мы достигли того, что нам нужно, без массовых обновлений всех сервисов и в тоже время имеем UI мобильной навигации.
Вы возможно спросите: «А что с миганием интерфейса? Если сервер отдает одно, а клиент рендерит другое - будут блики». Ответ, да, но есть «НО» и это — CSS, которым мы скрываем интерфейс в мобильной версии, а в десктопе отображаем. Тем самым, ни на мобильной версии, на на десктопной версии бликов интерфейса нет.
Хитрости
Внимательный читатель мог заметить, что в самом начале было сказано, что nginx
отвечает за раздачу статики, но мы создали дополнительный endpoint, который предоставляет статику через Node.JS. И можно сказать, что Node.JS неэффективен в раздаче файлов, и это полностью верно. Мы осознавали это и решили настроить наш новый endpoint так, чтобы он не раздавал статику напрямую, а перенаправлял нас к соответствующему файлу, который уже будет отдан через nginx
.
Заключение
В данной статье мы рассмотрели опыт внедрения mobile web в интернет банк для юридических лиц Альфа-Банка.
Стоит отметить, что целевым решением является использование обновленного контракта shared#getResources
и это было реализовано в библиотеке, которая обновляется довольно часто и не заставит себя ждать.
Вот, что у нас получилось.
Возможно, данный опыт будет полезен кому-то или подтолкнет к новым идеям по реализации простых, но неочевидных решений.
Спасибо за внимание! С наступающим Новым годом