Сейчас понятие «микрофронтенды» встречается довольно часто, но что это такое и какие задачи они решают? Зачем нам микрофронтенды, если есть микросервисы или монолит? И стоит ли тащить микрофронтенды в свой проект только потому, что это модно? Расскажу об этом, а также о трёх способах организации микрофронтендов: Podium, Single-SPA и Module Federation. Какой среди них лучший и нашли ли разработчики в нём панацею? Об этом читайте под катом.

Перед тем как начать, буквально пару слов о себе. Меня зовут Зар Захаров, я ведущий разработчик ВКонтакте. Занимаюсь разработкой с 2006 года, когда ещё выходили статьи с заголовками «Блочная вёрстка: миф или реальность». С тех пор я работал в стартапах, крупных enterprise-проектах, на фрилансе и в аутсорсе. За это время накопил большой опыт, который позволяет сравнить разные способы разработки приложений и рассказать об их плюсах и минусах. 

Что будет в статье

Архитектуры приложений

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

  • множество библиотек и, как следствие, большое количество зависимостей;

  • один общий store, который помогает нам обмениваться между компонентами, страницами и т. д., а также хранить информацию из базы данных;

  • разные компоненты, которые необходимо собрать вместе, чтобы сделать из них приложение.

Собрать все эти части можно несколькими способами. Остановлюсь на каждом подробно.

Монолит

Когда мы только начинаем разрабатывать приложение, есть несколько вариантов, как его собрать. Один из них — монолит. Представим, что мы сделали MainPage — главную страницу.

У MainPage есть внутренний роутинг, но пока всё равно это одна страница. А что, если сделать вторую, например AboutPage? Тогда все компоненты, библиотеки и хранилище придётся расширять.

То же самое будет, если мы добавим ещё кучу страниц. Это и называется монолитом. 

У монолитной архитектуры есть свои плюсы:

  • её легче реализовать;

  • доступны E2E-тесты;

  • её легко развёртывать и масштабировать;

  • можно использовать единый фреймворк.

Но у монолита есть и недостатки:

  • он часто перерождается в «большой шарик грязи» — становится так много всего, что непонятно, как со всем этим работать;

  • его компоненты растут и меняются вместе с ним — когда мы поменяли что-то одно в архитектуре, надо менять и другие части;

  • масштабирование может быть проблематичным: чем больше проект, тем сложнее с ним взаимодействовать;

  • в монолите практически нет изоляции, и все части сильно зависят друг от друга — если что-то произойдёт с одной частью, то и другие тоже встанут.

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

Микросервисы

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

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

Можно просто скопировать его из MainPage в AboutPage, но тогда при каждом обновлении компонента на первой странице придётся вручную обновлять его и на второй. А мы хотим, чтобы это происходило автоматически. Как это сделать? Есть несколько способов: NPM, ESI и MFE.

Самый первый и простой вариант — вынести общие компоненты в NPM-пакет, то есть в библиотеку компонентов. А затем открыть доступ к нему разным страницам и поставить в приложение как зависимость.

Такое решение работает, но есть несколько проблем: 

  • во-первых, NPM-пакеты нужно обновлять. На первой странице команда может, например, поменять дизайн, и тогда ей придётся договариваться с другой командой об изменении компонента в NPM-пакете;

  • во-вторых, при обновлении версии лишь одной страницы могут возникать breaking change — серьёзные изменения, которые могут повлиять на работоспособность других частей приложения. В таком случае компонент в NPM-пакете тоже необходимо обновлять.

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

Менее известный способ — Edge Side Includes (ESI). Принцип его использования напоминает iFrame — компоненты встраиваются в код. Мы передаём HTML-элементы и получаем их в HTML-коде.

Минус ESI в том, что он работает не в runtime, а в момент генерации страницы. Такой вариант не очень удобен, потому что, например, библиотека React, фреймворки Angular и Vue обновляются в runtime.

Есть ещё один классный подход для решения проблемы — микрофронтенды (MFE)

MFE позволяет передавать доступный для всех компонент, который будет обновляться одновременно со всеми. На этом подходе остановимся подробнее.

Что такое микрофронтенды

Представьте несколько абсолютно разных страниц, которые написаны на разных фреймворках, но объединены в одно приложение. Это и есть MFE, или микрофронтенды.

Основная идея технологии хорошо описана в книге Майкла Гирса Micro Frontends in action«…думать о веб-сайте или веб-приложении как о совокупности функций, которые принадлежат независимым командам».

Какие задачи решают микрофронтенды

Микрофронтенд удобен для проектов, которые выстраивают у себя Single Page Application (SPA) и в которых разные команды пытаются экспериментировать с подходами. Например, YouTube использует микрофронтенд. 

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

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

Подходы к реализации микрофронтендов

Расскажу о трёх решениях, как можно реализовать MFE: Podium, Single-SPA и Module Federation — от самого малоизвестного до самого популярного. 

Первый подход — это Podium, фреймворк для создания микрофронтендов на стороне сервера. Он реализован на JavaScript и работает на Node.js, а также входит в коробку фреймворка hapi. Это интересный подход, но с ним есть некоторые сложности. Podium подходит только для фулстек-разработчиков, так как необходимо писать не только фронтенд, но и бэкенд на Node.js. Код с использованием Podium выглядит вот так.

На скриншоте мы создаём layout’ы, подключаем и регистрируем другие приложения, которые можно спокойно открыть по URL. Ничего сложного. Если хочется создать небольшое приложение, этот подход вполне можно использовать.

Второй подход самый популярный — это фреймворк Single-SPA. О нём сейчас очень много говорят и пишут, а на его официальном сайте есть обучающие видео для новичков и профессионалов. Этот фреймворк объединяет несколько JavaScript-микрофронтендов в одно фронтенд-приложение. Он позволяет использовать на одной странице несколько фреймворков: например, AngularJS, Angular, Ember.js или другие. И разворачивать их независимо друг от друга.

Single-SPA действительно удобен в использовании, потому что не надо ничего переписывать, чтобы им пользоваться. Однажды я работал над проектом, который был написан на AngularJS, и бизнес захотел перевести все новые функции на React. Тогда-то нас и спас Single-SPA. Нужно было лишь «воткнуть» его в старое приложение, сделать общую шапку или навигацию — и всё готово. Выглядело это вот так.

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

Можно придумать много идей, как это сделать, например, с помощью local или global storage. Я встречал подход, когда использовали глобальный объект window, что тоже не лишено смысла, хотя и небезопасно. Подходы разные, но проблема всё равно остаётся. Чтобы найти решение, придётся потратить немало времени и придумать свои костыли. 

Третий подход — Module Federation, в последнее время о нём всё чаще говорят. Его активно развивают, он даже вошёл в пятый webpack как плагин. Остановлюсь на этом подходе немного подробнее.

Module Federation

Module Federation позволяет приложению экспортировать один или несколько модулей в отдельные js-файлы. Это отличный способ строить микрофронтенд-приложение. При этом можно импортироваться в сторонние модули — работу с зависимостями webpack берёт на себя. Также, в отличие от NPM, Module Federation работает в runtime. 

Как работает Module Federation

Допустим, нам нужно, чтобы один и тот же компонент, который есть в MainPage, отобразился в AboutPage. А потом мы хотим сделать то же самое, но наоборот — отобразить компонент из AboutPage в MainPage.

Вот как это выглядит в коде.

Сначала чуть-чуть настраивается webpack.config. На скриншоте есть имя — это ключ, по которому мы ориентируемся. Есть файл, который будет генерироваться после того, как соберётся плагин webpack. Также указано, что именно и под каким именем подключать, в какую папку смотреть и что нужно импортировать. Ещё есть shared, в котором мы прописываем, что в нескольких приложениях используются одинаковые библиотеки, чтобы лишний раз не собирать бандл. Для этого плагин подключает разные файлы на разные хосты.

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

А дальше просто используем это в своём приложении с помощью обращения к компоненту через home/ProductCarousel.

Так довольно легко получить доступ к другому компоненту. Это может быть приложение, store или вообще любой js-файл.

Пример использования MF из практики

Я работал с приложением, которое было целиком и полностью написано на Node.js. Это было серверное приложение с клиентом, где всё генерировалось через handlebars.

Его код было очень сложно читать — можно было месяцами разбираться со взаимосвязями. Из-за этого долго происходил онбординг новых людей в команду — им надо было очень много всего объяснять. Мы хотели переехать на новую систему. Но бизнесу это было невыгодно — останавливать разработку на полгода или год было бы потерей денег.

Мы рассматривали разные варианты того, что можно было сделать. Single-SPA не подходил — всё равно онбординг оставался бы сложным. В итоге мы использовали Module Federation и решили собирать приложение отдельными компонентами, не затрагивая центральную часть Canvas, которая была написана неплохо. Шапку мы создали как отдельный проект, боковое меню — тоже. 

Чтобы приложения между собой взаимодействовали, мы создали общий storage. Он мог подключаться ко всем приложениям. Если требовалось обновить, например, шапку, то нам не нужно было больше обновлять вообще всё.

Выводы

Микрофронтенд имеет смысл, когда недостатки монолита начинают преобладать над преимуществами. Например, когда вы сталкиваетесь с такими проблемами:

  • есть несколько команд, которые делают одно дело и постоянно спотыкаются друг об друга, конфликтуют;

  • доставка до продакшена стала очень долгая и фичи реализуются с задержкой;

  • преимущества монолита становятся его недостатками.

Во всех этих ситуациях стоит задуматься о микрофронтендах. А вот выбор подхода к реализации зависит от проекта и задач. Теперь вы знаете как минимум три разных подхода.

Нельзя сказать, что микрофронтенды — это панацея от всего и гарантированное спасение для вашего приложения. Технологию нужно выбирать с умом и подходить к этому правильно. Микрофронтенды — это просто один из подходов к разработке, у каждого из них есть преимущества и подводные камни. Перед тем как переходить на него, стоит задуматься, а не усложнит ли он вам жизнь? Мы ВКонтакте пока остаёмся в монолите, но постоянно посматриваем в сторону микросервисов и микрофронтендов и думаем, как можно их использовать у нас. 

Эта статья написана по мотивам доклада на FrontendConf 2022, где, кстати, у VK была крутая зона для фана в перерывах между докладами. А следующая наша масштабная конференция по фронтенду состоится в октябре, и мы уже вовсю составляем программу. Изучайте темы и подавайте заявки до 10 мая. И подписывайтесь на мой телеграм-канал — в нём я рассказываю много интересного о мире фронтенд-разработки и вообще о технологиях.

Комментарии (12)


  1. To4KaXD
    20.04.2023 15:24

    Спасибо за интересную статью!


  1. gmtd
    20.04.2023 15:24
    +1

    Представьте несколько абсолютно разных страниц, которые написаны на разных фреймворках, но объединены в одно приложение. Это и есть MFE, или микрофронтенды.

    Странное определение

    А если у меня веб приложение на одном Vue, и в нем две слабо связанные части которые независимо пишут две команды - это не микрофронтенды?


  1. nin-jin
    20.04.2023 15:24
    +7

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


    1. Riman
      20.04.2023 15:24
      +4

      Не сомневаемся, что Вы бы сделали лучше...


      1. nin-jin
        20.04.2023 15:24
        +1


      1. ris58h
        20.04.2023 15:24
        +2

        У меня троеточия под видео появляются через 7 секунд после начала загрузки страницы. Думаю, что возможно сделать лучше (быстрее).


  1. nin-jin
    20.04.2023 15:24
    +4

    один общий store, который помогает нам обмениваться между компонентами, страницами и т. д., а также хранить информацию из базы данных;

    А вы не пробовали не класть все яйца в одну корзину? Ну там написать полноценную (не тупую) модель предметной области и тд.

    в монолите практически нет изоляции, и все части сильно зависят друг от друга — если что-то произойдёт с одной частью, то и другие тоже встанут.

    Похоже ваш фреймворк не умеет адекватно обрабатывать исключительные ситуации. Может ну его?

    Представьте несколько абсолютно разных страниц, которые написаны на разных фреймворках, но объединены в одно приложение.

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


  1. Riman
    20.04.2023 15:24
    -3

    Зар спасибо Вам за статью!

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


  1. php7
    20.04.2023 15:24
    +1

    Допускаю это:

    есть несколько команд, которые делают одно дело и постоянно спотыкаются друг об друга, конфликтуют;

    Может там, где спотыкаются, выделить новую команду?

    И почему перестанут конфликтовать на общем функционале, он же останется?


    1. php7
      20.04.2023 15:24

      Кстати, конфликты и у одной команда бывают.


  1. JSmitty
    20.04.2023 15:24
    +1

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

    Разработка опциональных модулей ведется "внешними" (по отношению к нашему проекту) командами, в сторонних репозиториях, и развязывает их релизный цикл с нашим. Часть нашей внутренней кухни также выполнена как MF модули, с тем чтобы аналогично сделать загрузку опциональной, иметь возможность безболезненно дропнуть их (когда потребуется) - унифицированно с остальными модулями. Хост MF при этом остается практически чистым от добавляемой логики доп. модулей - что очень хорошо для масштабирования проекта.

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

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

    Отдельно стоит упомянуть, что MF позволяет модулям переиспользовать библиотеки хоста, чем мы с удовольствием пользуемся, уменьшая объем загрузки кода в рантайме - разделяя тот же react, lodash/ramda и прочие библиотеки.


  1. noodles
    20.04.2023 15:24

    • есть несколько команд, которые делают одно дело и постоянно спотыкаются друг об друга, конфликтуют;

    неужели нельзя это решить административными способами?

    Команда Х - вот, разрабатывайте пожалуйста компонент\страницу Х. А вы команда Y - пожалуйста, разрабатывайте компонент\страницу Y. И не мешайте друг-другу!
    У кого возникнет пересекающиеся вопросы - назначте совещание - обсудим.

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