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



Микрофронтенды на iframe


В одной компании взвешенным и обдуманным решением CTO было принято, что микрофронтендам наравне с микросервисами быть, причем сервировать их надо на iframe'ах.

В качестве аргумента, кстати, был приведен продукт Office 360 от Microsoft, раньше там использовался `<iframe />` для верхнего и бокового меню. Теперь iframe'ов там нет.

Причины и предпосылки для микрофронтендов


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

Все имеющиеся приложения — это React SPA. Ничего общего кроме Material UI, React Router v4 и зародыша UI-kit в качестве npm-модулей, не имеют.

Часть приложений будет использоваться и поставляться как в standalone-варианте, так и как часть другого приложения.

Микрофронтенды были поделены по крупным функциональным блокам:

  1. Шапка приложения. Роутинг между приложениями
  2. Приложение-дэшбоард. С метрикой и виджетами. Каждый виджет дэшборда должен быть частью соответсвующего приложения (подключаться по iframe).
  3. Сами приложения. Некоторые из них включают части друг друга (но без фанатизма и рекурсии)



Таким образом, получаются разные релизные циклы, не зависящие друг от друга. Лишь бы приложения следовали внутреннему API.

Проблема #0. Неправильное разделение на микрофронтенды


К сожалению, микрофронтенды не были продуманы достаточно глубого. В качестве примера можно привести интернет-магазин. Кнопка «купить» и корзина могут быть разбросаны по множеству мест, но все они — один микрофронтенд. Как карточка товара в 10 вариациях, так и процесс оформления заказа (где платежки и адреса). Все это могут быть отдельными микрофронтендами со своими релизными циклами.



В реальности, получилось так, что приложения разделили очень грубо. В аналогии с интернет-магазином — это когда страницу корзины и оформления заказа делает одна команда, а все остальное — вторая команда (включая счетчики корзины на всех остальных страницах). При этом все используют одну и ту же бизнес-логику, а интерфейс переиспользуется на уровне UI-kit, или библиотеки Material-UI.

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

Как результат, экономия времени на переиспользовании приложений не удалась. Дублирование функционала все также осталось на высоком уровне. Использование микрофронтендов без iframe или компонентов с более сложной логикой из расширенного UI-kit могло бы решить проблему дублирования функионала.

Проблема #1. Отсутствие оркестрации процесса


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

Чтобы нивелировать все CORS проблемы раз и навсегда, было принято решение посадить nginx, который бы разруливал маршрутизацию. Таким образом, каждый микрофронтенд и каждый микросервис имел свой адрес, например:

https://domain.zone/dashboard
https://domain.zone/header
https://domain.zone/app1
https://domain.zone/app2
https://domain.zone/api1
https://domain.zone/api2

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

Здесь приходит на помощь пакет `http-proxy-middleware` которое можно настроить в паре с CRA. Оказалось, только половина front-end разработчиков оказалось в силе настроить такой сетап. Никого обвинять здесь, разумеется, нельзя, но такая проблема появилась, а решать ее надо организационно.

Также необходима четкая версионность всех приложений с описанием функционала, доступных методов и внутреннего API. Отсюда вытекает следующая проблема: документация.

Проблема #2. Отсутствие внутреннего API


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

Это очень критическая часть системы в случае распределенных команд, да еще с учетом текучки кадров.

Также необходимо разработать механизм взаимодействия между приложениями. Тут на помощь приходит `postMessage API` или же прямой доступ к другому, встроенному почти в каждое React-приложение — Redux. Чем не message bus? Но об этом позже.

Проблема #3. Iframe не обладает достаточной гибкостью


В использовании тега `<iframe />` нет ничего плохого. Это мощный инструмент с встроенным message bus (postMessage API) и широкой настройкой безопасности.

Однако, в случае микрофронтендов, `<iframe />` накладывает большое количество ограничений. Одно из них — невозможность переиспользовать одно приложение в нескольких частях страницы.

Переиспользование приложений

В случае с аналогией интернет-магазина, 10 кнопок «Купить» создадут 10 `<iframe />`, то есть 10 запущенных React-приложений. Никакой памяти на это не хватит. Это одна из причин, по которой разделение приложения на команды по фичам не выдается возможным.

URL в качестве управлением стейтом не подходит

Мы все привыкли к роутингу приложений через URL. Это удобно и в случае использования микрофронтенда как самостоятельной единицы. Например, когда часть основного приложения, является достаточно целостной, чтобы быть полезной в одиночку. Это, конечно, не уникальное преимущество iframe-подхода, но это делать довольно просто.

Вот пример, как то фиолетовое приложение из КДПВ с другим URL может работать как standalone приложение:



Однако, использовать URL интерфейс iframe для переключения состояния микрофронтенда в нашем случае оказалось невозможным: микрофронтенд начинает грузиться с нуля из-за неполной интеграции history API с его `pushState`и React Router — получаем полное обновление страницы.

Обработчики кликов вне области `iframe`

Представьте, что вы заходите сделать выпадающее меню. Как на схеме выше: из розового меню. А еще и закрывать его по клику по пустому месту. В случае с iframe, вам необходимо использовать условный postMessage API, так как клик «вне» по просту не распознается из-за разных объектов window. Либо же придумывать хак с прозрачным фоном увеличенного элемента iframe во весь экран.

Кстати, изменение размеров iframe и подстраивание под него родительского приложения тоже становится более громоздкой и сложной.

Бонусная проблема: Несоразмерное использоваение Cookie


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

Было решено записывать в авторизационные куки не только токен, но и полный список прав на те или иные части приложения. Все это шифровалось SHA-??? и конвертировалось в base64.
Как результат, размер кук превышал 8KB, что является значением по умолчанию для nodejs/nginx, (или 2KB для размера одной записи Cookie в Google Chrome), что привело более сложной настройке серверов (без eject CRA уже не запустить с такой настройкой), а также к разделению этого большого зашифрованного массива данных на более мелкие куки-строки.

А это означает, что каждый запрос к серверу, даже для получения `favicon.ico` или получения списка доступных разделов меню, оснащено дополнительным хедером внушительного размера.

Заключение. Как тогда жить с микрофронтендами?


Для начала, конечно, нужно определиться с тем, а нужны ли микрофронтенды? Зачастую, правильно настроенный и обогащенный компонентами UI-kit в виде npm модуля решает проблему и независимых релизов, и одинакового визуального стиля.

  • Не использовать iframe. Он не упрощает работу, а лишь добавляет проблем с производительностью, сильно ограничивая в возможности разбиения приложения на микрофронтенды. Рендеринг кусков SPA в специально-зарезервированные теги куда более эффективное решение.
  • Разработать оркестрацию процесса: как в продакшене, так и для разработки. Не каждый разработчик захочет лезть в смежную отрасль маршрутизации и прокси, когда он нанимался клепать интерфейсы из готовых блоков.
  • Разработать message bus между приложениями. Это намного проще в случае единого глобального пространства — объекта window.
  • Создать документацию по интерфейсу взаимодействия приложений, а также их запуска и настройки.