company_banner

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

Автор оригинала: Ö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
Райффайзенбанк
147,02
Развеиваем мифы об IT в банках
Поделиться публикацией

Комментарии 18

    0
    Занимаюсь последнее время как раз этой темой. Автор пишет очень по делу, но только краем затрагивает пару очень важных вопросов: базовую среду микроприложений и общую UI-библиотеку. Почему это важно — потому что собственно загрузчики/оркестраторы довольно абстрактны и делаются на чистом JS/TS, контракты и общая шина (шины) тоже в общем-то проблем не представляют, а вот динамическое монтирование/отмонтирование в случае SPA сразу же ограничивает полёт фантазии…

    Но я так понимаю, это связано со спецификой SSR-ориентированной архитектуры у автора. Пойду почитаю, что там внутри.
      +6
      Ждём переизобретение SSI? (server-side includes)
        0
        А может просто не делать фронтенд таким огромным?
          –5

          А может стоило использовать такой фреймворк, где изначально продумана система горизонтального масштабирования разработки? Например, в $mol вы можете независимо разрабатывать несколько приложений и отдельно разрабатывать обвязку, которая объединяет разрозненные приложения в единый портал. Например, берём вот эти приложения, реализованные независимо, без оглядки на объединение:



          50 строчек кода и у нас получился единый портал, где они замечательно друг с другом сынтегрированы.

            0

            У меня сейчас постоянно стоит задача генерировать огромное количество CRUD интерфейсов, которые изредка обрастают доп логикой. Насколько с такой задачай справится mol?

              0

              Отлично справится. Можно сделать виджет редактирования сущности, который берёт описание схемы сущности и строит по нему форму редактирования. Так, например, сделан редактор компонент. Он анализирует код компонент и строит интерфейс для их настройки.

            +3

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

              +1
              Не надо останавливаться, после микросервисов, непременно, нужно притянуть и serverless )
                +5

                Чем эти микрофронтенды отличаются от классических модулей? На бэке понятно: у каждого микросервиса свой процесс, взаимодействие осуществляется средствами межпроцессной коммуникации (чаще всего по сети). Что у нас в случае микрофронтенд SPA? Модули фронта, которые общаются между собой по сети через сервер?

                  0
                  Мало чем, в моем понимании микрофронтенд это продолжение бэк микросервисов, только уже визуальная часть. Но изюм в том что на фронте пользователю хочется, чтобы у него был единый процесс, а не отдельно модуль товар, модуль доставка, модуль оплата. Нам же как разработчикам удобней разделить сущности, более того, чтобы разные сущности разрабатывали разные команды, да еще чтобы на разных технологиях. Но все же где то должна быть прослойка которая связывает эти модули, автор и рассуждает в этом направлении. Граница между «логика на бэке», «фронт только визуал» стирается. Есть разные потребности бизнеса и есть разные способы их достижения. Одно из предположений, что микрофронтенды позволят совмещать разные подходы под одной крышей с минимум боли.
                    +1

                    Возникают еще проблемы, не озвученные в статье:


                    • Для каждого микро-приложения используются свои технологии. Как уменьшить размер бандлов, загружаемых на клиент? Ведь у нас для каждой фичи свой фреймворк. А может даже разные версии одной библиотеки. На беке то такой проблемы нет.
                    • У нас вдруг запланирован редизайн. Как согласованно обновить N разных приложений со своим подходом к стилям? Или это должны делать N команд параллельно? Не слишком ли расточительно?
                    • Вряд ли все микро-приложения будут активно развиваться в течение всего жизненного цикла продукта. Большая часть это сделал — забыл. А основная активность сосредоточена только в паре-тройке разделов. Как тогда управлять техническим долгом, обновлять зависимости? Не окажемся ли мы потом с кучей разрозненного хлама?

                    Последнее мне вообще напомнило разработку на классических серверных MVC фреймворках лет 7 назад. Когда разные страницы имели свой независимый набор подключенных JS-библиотек.


                    Такое чувство, что этот подход применим только для каких то очень больших сайтов, где параллельно только фронтом должны заниматься десятки человек. Или наоборот, когда нужно быстро наваять среднее приложение, а договариваться об архитектуре не хочестя. Главное, чтобы потом поддерживать это приложение не тебе =)

                      0
                      У нас вдруг запланирован редизайн. Как согласованно обновить N разных приложений со своим подходом к стилям? Или это должны делать N команд параллельно? Не слишком ли расточительно?

                      У нас на бэке вдруг запланирован переход с RESTish на GraphQL или даже grpc, как согласованно обновить N разных приложений со своим подходом к работе с внешними API. Или это должны делать N команд параллельно? Не слишком ли расточительно?


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

                      У нас на бэке… Ну вы поняли :)


                      P.S. Я понимаю, что проблемы фронта сложнее аналогичных на бэке, всё-таки в одном пространстве эти микрофронтенды работают и без специальных усилий по изоляции очень быстро получим, как мнимум, конфликты. Где-то конфликты версий, а где-то синглтоны одного микрофронта будут работать со вторым неожижанно для обоих

                        0

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


                        А тут просто какое-то натягивание архитектуры фронта на структуру отделов компании-разработчика.

                          0

                          Ну, одно из обоснований перехода на микросервисы (на бэке) не повышение и балансировка, а чёткое разделение ответственностей "отделов". Даже одно из определений микросервиса гласит что-то вроде "микросервис — изолированный сервис, который легко пишется и(или) поддерживается одной командой (не помню, 3-12 человек из определения этого у меня в голове, или просто инфа о нормльном размере команды)".


                          На крупном проекте архитектура фронта, позволяющая раделить влияние модулей, разрабатываемых разными фронт или фулстэк командами, очень крутая штука. Боюсь только, что на том уровне как на бэке — слабодостижимая. Не уверен даже, что можно нормально использовать в одном проекте на одной версии фреймворка для одного модуля JS, а для другого TS. JS-команда скорее всего откажется писать декларации типов для свогео модуля, а TS либо будет писать их проклиная JS, или передут на implicit any, сначала только для того модуля, а потом начнётся протечка и в TS.

                  0
                  Хм… чтобы осознать плюсы-минусы данной «идеи» необходимо больше менее абстрактной конкретики.

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

                  На том же Angular 2+ при правильно спланированной архитектуре нет никаких проблем «разбить» монолит на полностью автономные модули, зависящие только от общих сервисов(«ядра»).

                  При некотором желании, не проблема организовать автоматизированную систему сборки «микрофронтов» в конечное приложение.
                    0

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

                      +2

                      А есть где посмотреть более-менее реальный пример, хотя бы с разными версиями одного фреймворка?

                        0

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


                        P.S. а если серьезно, можно попробовать посмотреть на Wrike, у них раньше был микс ExtJS + Angular, не знаю как сейчас.

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

                  Самое читаемое