Как стать автором
Обновить
0
Райффайзен Банк
Развеиваем мифы об IT в банках

Микрофронтенды: о чем это мы?

Время на прочтение 10 мин
Количество просмотров 67K
Автор оригинала: Öner Zafer
Все эти годы вы, frontend-разработчик, писали монолиты, хотя и понимали, что это дурная привычка. Вы делили свой код на компоненты, использовали require или import и определяли npm-пакеты в package.json или плодили гит-репозитории в вашем проекте, но все равно писали монолит.
Пришло время изменить положение.

Почему ваш код можно считать монолитом?


По своей природе все frontend-приложения монолитны – кроме приложений, реализующих микро-фронтенды. Причина в том, что вы разрабатываете с использованием библиотеки React, и работу ведут две команды. Обе должны использовать одну версию React и держать друг друга в курсе обновлений, а значит, неизменно решать конфликты при мерже кода. Они не полностью независимы друг от друга в кодовой базе. Вероятно, они вообще пользуются одним репозиторием и одной системой сборки. Микросервисы могут спасти от монолитности приложения! Но как же так? Ведь они для бэкенда! *неимоверное удивление*

Что же такое микросервисы?


Говоря простым языком, микросервисы — это техника разработки, которая позволяет разработчикам делать независимые поставки функционала (релизы) для разных частей платформы, и при этом релизы не ломают друг друга. Независимые поставки позволяют им собирать изолированные или слабосвязанные сервисы. Есть несколько правил, делающих такую архитектуру устойчивее. Вкратце их можно определить так: каждый сервис должен быть маленьким и выполнять лишь одну задачу. Следовательно, работающая над ним команда тоже должна быть маленькой. Насколько крупными могут быть проект и команда, объясняют Джеймс Льюис и Мартин Фаулер:

Разработчики, взаимодействующие с микросервисами, называют разные размеры. Самые крупные из них отвечают стратегии Amazon о «команде на две пиццы» — не более 10-12 человек. Обратный полюс – команды из 5-6 человек, где каждый поддерживает один сервис.

Вот схема, объясняющая отличие монолита от микросервисов:



Из схемы видно, что каждый сервис в системе микросервисов является отдельным приложением, кроме UI — он остался единым целым! Когда все сервисы поддерживаются одной командой, велик риск, что по мере роста компании frontend-команда перестанет за UI успевать. В этом состоит уязвимость данной архитектуры.



Архитектура может принести и организационные проблемы. Предположим, что компания выросла и взяла на вооружение гибкие методологии разработки (это я про Agile). Они требуют небольших кросс-функциональных команд. Конечно, в нашем абстрактном примере руководители начнут разделять задачи frontend’а и backend’а, и кросс-функциональные команды не будут по-настоящему кросс-функциональны. И все усилия будут тщетными: команда может выглядеть гибкой, но на деле будет сильно разделена. Управление подобной командой не для слабонервных. На каждой планерке будет вставать вопрос: достаточно ли frontend-задач, достаточно ли backend-задач в спринте? Для решения этих и многих других проблем пару лет назад возникла идея микрофронтедов, быстро завоевавшая популярность.

Решение проблемы: микрофронтенды


Решение выглядит довольно очевидно, ведь аналогичные принципы давно и успешно применялись в работе над backend-сервисами: разделить монолитный фронтенд на небольшие UI-фрагменты. Однако UI не совсем похож на сервисы – это интерфейс между конечным пользователем и продуктом, он должен быть продуманным и системным. Более того, в эпоху одностраничных приложений, целые приложения запускаются через браузер на клиентской стороне. Это уже не простые HTML-файлы, это сложные компоненты, которые могут заключать в себе различную UI и бизнес-логику. Теперь, пожалуй, необходимо дать определение микрофронтендам.

Принцип микрофронтендов: представление вебсайта или веб-приложения как набор функций, за которые отвечают независимые команды. У каждой из команд есть своя миссия, свое поле работы, на котором она специализируется. Команда кросс-функциональна и разрабатывает
весь цикл – от базы данных до пользовательского интерфейса (micro-fontend.org).

Мой текущий опыт показывает, что многим компаниям может быть сложно принять предложенную выше архитектуру. Другим — груз старого кода не дает перейти на новую архитектуру. Поэтому необходим более плавный, легкий и надежный способ миграции. Подробнее рассмотрев архитектуру, я попробую предложить свое видение решения проблемы. Прежде чем углубиться в детали, познакомимся с терминологией.

Общая структура и еще немного терминологии

Представим, что мы разделяем структуру монолитного приложения по вертикали, по бизнес-функциям. Мы получим несколько более мелких приложений с той же структурой, что и у монолитного приложения. Но если мы добавим специальное приложение поверх этих небольших монолитных приложений, то пользователи будут взаимодействовать с ним. Оно, в свою очередь, объединит UI тех маленьких приложений. Назовем этот уровень связующим, ведь он берет UI-элементы каждого микросервиса и соединяет их в единый интерфейс – вот самая прямая реализация микрофронтенда. *искреннее восхищение*



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

Проблемы, которые нужно решить


Когда родилась идея данной статьи, я завел на Reddit тему для ее обсуждения. Благодаря участникам сообщества и их откликам, я могу привести список проблем, требующих решения.

Проблема №1: добиться цельного и согласованного поведения от UI, когда у нас несколько абсолютно автономных микроприложений

Панацеи не существует, но есть идея создать общую UI-библиотеку, которая тоже бы являлась независимым микроприложением. В таком случае, все остальные микроприложения должны будут зависить от этой UI-библиотеки. И это убивает их независимость.

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

Либо же мы можем сделать SASS-переменные и примеси общими для всех команд. Среди минусов данного подхода будут повторяющаяся реализация UI-элементов и необходимость постоянной проверки дизайна сходных элементов во всех микроприложениях.

Проблема №2: убедиться, что одна команда не переписывает CSS другой команды

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

Во-вторых, можно заставить каждое микроприложение стать кастомным веб-компонентом. Преимущество такого подхода в том, что ограничением занимается браузер. Однако у всего есть цена: с Shadow DOM почти невозможно проводить рендеринг на стороне сервера. К тому же кастомные элементы не поддерживаются браузерами на 100% — тем более, если вам нужна поддержка IE.

Проблема №3: сделать глобальную информацию общей для разных микроприложений

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

Также вам может помочь реализация pub-sub или T39. Если вам нужен более тонкий обработчик глобальных состояний, можно реализовать небольшой общий Redux – таким образом получается более реактивная архитектура.

Проблема №4: если все микроприложения автономны, как проводить маршрутизацию на стороне клиента?

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

Мой прагматический подход заключается в создании общего клиентского маршрутизатора, ответственного только за маршруты верхнего уровня, а остальное отдано на откуп соответствующим микроприложениям. Допустим, у нас есть определение маршрута /content/:id. Общий маршрутизатор решит часть c /content, и решенный маршрут будет передан ContentMicroApp. ContentMicroApp – автономный сервер, который будет вызываться только с /:id.

Проблема №5: а точно ли нам нужна SSR (server-side rendering), возможна ли она при использовании микрофронтендов?

Рендер на стороне сервера – дело непростое. Если вы хотите связать микроприложения с помощью iframes, забудьте о рендеринге на стороне сервера. Аналогично, веб-компоненты для связывания не сильнее iframes. Однако если каждое из микроприложений способно рендерить контент на стороне сервера, то связующий слой будет отвечать только за объединение HTML-фрагментов на стороне сервера.

Проблема №6: «Интеграция с имеющимся окружением нужна как воздух! Как ее произвести?»

Для интеграции с имеющимся системами, я хочу описать свое видение, которое я называю “постепенным внедрением”.

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

Следующая ступень – постепенное внедрение. Мы возьмем небольшой кусочек LegacyMicroApp, удалив главную навигацию, заменив ее зависимостью. Эта зависимость – микроприложение, реализованное с помощью новенькой блестящей технологии, NavigationMicroApp.

Теперь LegacyMicroApp будет перехватывать все роуты через зависимость NavigationMicroApp и обрабатывать уже внутри себя.

Затем аналогичным способом мы переделаем футер.

Так мы будем продолжать откусывать от LegacyMicroApp по кусочку, пока от него ничего не останется.

Проблема №7: оркестровать сторону клиента, чтобы не приходилось каждый раз перезагружать страницу

Связующий слой решает проблемы на стороне клиента, но не на стороне сервера. На стороне клиента мы, загрузив единый HTML, не можем загружать при смене URL отдельные части. Следовательно, нам нужен механизм, который загружает фрагменты асинхронно. Проблема в том, что у этих фрагментов могут быть зависимости, и эти зависимости нужно уметь разрешать на стороне клиента. Это означает, что микрофронтенд-решение должно предлагать механизм загрузки микроприложений и внедрения зависимостей (dependency injection).

Перечисленные выше проблемы можно объединить в следующие темы:

Сторона клиента

  • Оркестрация
  • Маршрутизация
  • Изоляция микроприложений
  • Взаимодействие приложений
  • Единство UI микроприложений

Сторона сервера

  • Серверный рендеринг
  • Маршрутизация
  • Управление зависимостями

Гибкая и мощная, но простая архитектура


Ради этого стоило перетерпеть начало статьи! Основные элементы и требования микрофронтенд-архитектуры наконец-то начали вырисовываться ;)

Руководствуясь обозначенными требованиями и вызывающими беспокойство вопросами, я начал разрабатывать решение под названием microfe. *предвосхищение фидбека*
Здесь я в общих чертах опишу архитектуру проекта, описав вкратце его основные компоненты.

Легче всего начать со стороны клиента, которая обладает тремя отдельными основными структурами: AppsManager, Loader, Router, а также одной дополнительной, MicroAppStore.



AppsManager
AppsManager – ядро оркестрации микро-приложений на стороне клиента. Основная задача AppsManager – создание дерева зависимостей. Как только все зависимости разрешены, AppsManager запускает микроприложение.

Loader
Еще одна важнейшая часть оркестровки клиентской стороны – Loader. Он отвечает за загрузки приложений для клиентской стороны.

Router
Для выполнения маршрутизации на стороне клиента я внедрил Router в microfe. В отличие от обычных маршрутизаторов стороны клиента, маршрутизатор microfe обладает ограниченным функционалом. Он обрабатывает не страницы, а микроприложения. Допустим, у нас есть URL /content/detail/13 и ContentMicroApp. В таком случае маршрутизатор microfe обработает URL до /content/* и вызовет часть ContentMicroApp /detail/13.

MicroAppStore
Для решения клиентского взаимодействия между микроприложениями я внедрил в microfe MicroAppStore. Он обладает сходным функционалом, что и библиотека Redux, но с одним нюансом: он более гибкий в отношении асинхронного изменения данных и объявления reducer’a.

***


Сторона сервера, возможно, немного более сложна в реализации, но имеет более простую структуру. Она состоит из двух основных частей – StitchingServer и MicroAppServer.

MicroAppServer




Минимально возможный функционал MicroAppServer можно выразить так: init и serve.
Когда MicroAppServer загружается, первое что он должен делать — это вызвать SticthingServer и зарегистрировать эндпоинт с объявленным микро-приложением. Оно определяет зависмости, типы и URL схемы MicroAppServer Думаю, что о serve рассказывать излишне – здесь ничего интересного.

StitchingServer




StitchingServer позволяет зарегистрировать endpoint в MicroAppServers. Когда MicroAppServer регистрируется в StichingServer, StichingServer записывает объявление MicroAppServer.

Позже StitchingServer использует объявление для разрешения MicroAppServices от требуемого URL.

Разрешив MicroAppServer и все его зависимости, в названиях всех соответствующих путей в CSS, JS и HTML появится соответствующий публичный URL. Дополнительный шаг – добавление к CSS-селекторам уникального префикса MicroAppServer для предотвращения конфликта между микроприложениями на стороне клиента.

Затем на сцену выходит главная задача StitchingServer: компоновка всех полученных частей и возврат цельной HTML-страницы.

Пара слов о других реализациях


Еще до того, как в 2016 году появился термин микрофронтенд, многие крупные компании пытались решать схожие проблемы – например, Facebook с его BigPipe.
Сейчас идея набирает обороты. Компании самого разного масштаба интересуются этой темой, инвестируя в нее время и деньги. Например, Zalando предоставила открытый код своего решения Project Mosaic. Могу сказать, что microfe и Project Mosaic следуют аналогичным подходам, но с некоторыми кардинальными отличиями. Если microfe прибегает к полностью децентрализованной маршрутизации для большей независимости каждого микроприложения, Project Mosaic предпочитает централизованную маршрутизацию и определение шаблона для каждого маршрута. Кстати говоря, Project Mosaic позволяет легко проводить АB-тестирование и динамическую генерацию шаблона прямо на лету.

Есть и другие подходы, в частности, использование ifram’ов в качестве связующего слоя – очевидно, не на стороне сервера, а на стороне клиента. Это очень простое решение, которое не требует особой серверной структуры и привлечения DevOps. Оно может быть реализовано фронтенд-командой самостоятельно, а значит, создает меньше организационных проблем для компании и стоит дешевле.

Еще существует фреймворк single-spa. Проект полагается на соглашения о наименованиях каждого приложения для разрешения и загрузки микроприложений. Легко уловить идею и следовать шаблонам. Так что фреймворк может быть полезным для знакомства и экспериментов над системой в вашей локальной среде. Минус проекта в том, что вам придется строить каждое микроприложения строго определенным путем – иначе, фреймворк может его не принять.

Заключение (и ссылки)


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

micro fe app registry server
micro front end infrastructure
Теги:
Хабы:
+21
Комментарии 18
Комментарии Комментарии 18

Публикации

Информация

Сайт
www.raiffeisen.ru
Дата регистрации
Дата основания
1996
Численность
5 001–10 000 человек
Местоположение
Россия
Представитель

Истории