Transcription
Пару лет назад все говорили о микрофронтендах, но сколько реальных проектов, использующих их, вы видели? В большинстве случаев они приносят больше проблем, чем решают. И я думаю, причина в том, что микрофронтенды в большинстве случаев просто не нужны. Но вот в чем дело: ИИ изменил то, как мы создаем программное обеспечение. Так изменил ли он то, когда нам следует использовать микрофронтенды? Сегодня мы рассмотрим, как создавать приложения с микрофронтендами в 2026 году. Какие существуют альтернативы и как ИИ изменил то, где нам следует их использовать, а где нет. Если вы здесь впервые, меня зовут Дима. Я старший фронтенд-разработчик с более чем 8-летним опытом работы в различных компаниях. На этом канале мы говорим об архитектуре фронтенда, веб-разработке и всем, что с этим связано. А теперь перейдем к слайдам.
Что такое микрофронтенды? Идея очень проста. Вы берете фронтенд-приложение и разбиваете его на более мелкие, независимо развертываемые части. Например, у вас есть большое финансовое приложение, и вы видите, что оно состоит из различных независимых частей, таких как счета, платежи, аналитика. Итак, вы решаете его разбить. Каждая часть обычно принадлежит отдельной команде. Хотя одна команда может владеть несколькими частями, и иногда это разбивается отдельными разработчиками, а не командами. Каждая часть может быть создана, протестирована и выпущена по собственному графику, не конфликтуя с другими частями приложения. Идея микрофронтендов происходит от бэкенд-микросервисов. Тот же подход к разделению большой системы на более мелкие независимые сервисы, но примененный к фронтенду.
Теперь, как это выглядит на практике? У каждого микрофронтенда может быть свой бэкенд, но это необязательно. Все микрофронтенды могут использовать один бэкенд, или один микрофронтенд может использовать два или три бэкенда. Возможны различные конфигурации. Что действительно важно, так это то, что у каждого микрофронтенда есть свой конвейер развертывания, свой автоматизированный процесс сборки, тестирования и развертывания кода. Когда мы развертываем одну часть, нам не нужно развертывать другие. Это дает нам гибкость. Если одна часть не готова к выпуску, команда может продолжать работать над ней, не блокируя другие части.
Когда команды выбирают микрофронтенды, какие проблемы они обычно пытаются решить? Две основные вещи. Во-первых, независимый цикл выпуска. Каждая команда создает и выпускает свою часть фронтенда по собственному графику, не дожидаясь других команд. Если одна команда готова к выпуску и, что более важно, им нужно что-то выпустить очень быстро, они не хотят быть заблокированными другой командой. Когда над одним приложением работают многие разработчики или даже несколько команд, такие ситуации встречаются часто. Но с микрофронтендами каждая часть приложения может быть развернута независимо. Во-вторых, выбор технологий. Если по какой-то причине нам нужно использовать разные технологические стеки в разных частях приложения, мы можем сделать это с помощью микрофронтендов. В других видео мы много говорили об архитектуре фронтенда и шаблонах проектирования. Поэтому нам нужно понять, какое место занимают микрофронтенды в этой структуре. Существует три уровня архитектуры. На нижнем уровне у нас есть шаблоны проектирования, такие как SOLID, GRASP, KISS. Все они касаются отдельных компонентов, классов, функций. На среднем уровне у нас есть архитектурные шаблоны, такие как дизайн срезов функций, вертикальные срезы, чистая архитектура. Все они касаются того, как мы организуем функции, слои и домены внутри приложения. А на верхнем уровне у нас есть высокоуровневая архитектура, такая как модульный монолит, микрофронтенды. Если наше приложение достаточно мало, у нас нет этого уровня, потому что у нас есть одно приложение, одна команда и одно развертывание, поэтому оно нам не нужно. Сегодня мы сосредоточимся на этом верхнем уровне.
Вот что мы сегодня рассмотрим. Мы начнем с того, что такое микрофронтенды, как команды их интегрируют и какие инструменты существуют в 2026 году. Затем мы рассмотрим распространенные проблемы: связь, развертывание и дублирование пакетов. После этого мы рассмотрим модульный монолит, который может дать нам большинство преимуществ микрофронтендов, но с меньшей сложностью. Так что иногда это может быть лучшим выбором, и мы рассмотрим случаи, когда это так. Затем мы рассмотрим, как инструменты кодирования на базе ИИ изменили наше представление об архитектуре, и особенно о микрофронтендах. И, наконец, я дам вам структуру принятия решений. Я покажу, когда, по моему мнению, вам следует использовать микрофронтенды или что-то другое, исходя из размера команды, типа продукта и других параметров.
Эй, ребята, быстро. У меня есть бесплатное руководство по современным шаблонам архитектуры фронтенда. Оно охватывает самые популярные подходы и когда их использовать. Ссылка в описании. И если вы серьезно относитесь к архитектуре фронтенда, у меня есть курс. Не просто теория, а с реальной практикой применения. Он также охватывает системный дизайн, CSS, тестирование и многое другое. Проверьте advancedfrontcourse.com. Ссылка также в описании.
Начнем с основ. Что такое микрофронтенды, как команды их интегрируют и какие инструменты существуют сегодня? Во-первых, нам нужно знать, что существуют разные способы организации фронтенд-приложения. Возможно, их больше. Я не говорю, что существуют только эти, но сегодня мы поговорим об этих трех, потому что, по моей практике, они кажутся наиболее популярными. Во-первых, монолитное приложение — это когда у вас одна кодовая база и одно развертывание. Все функции живут вместе, и между ними нет строгих границ. Далее, модульный монолит. Это все еще одно развертывание, но внутри приложение разделено на модули. Модули могут взаимодействовать друг с другом только через публичный API, а их внутренние части остаются скрытыми. И третье, микрофронтенды. Означает отдельные кодовые базы и отдельные развертывания. Каждая команда создает и выпускает независимо. Оболочечное приложение объединяет все в одно приложение для пользователя. Основное отличие между микрофронтендами и модульным монолитом заключается в том, что с микрофронтендами мы получаем независимое развертывание модулей. Вот почему на изображении модульный монолит находится между монолитным приложением и микрофронтендами.
Теперь давайте сравним эти три подхода бок о бок. С монолитом у нас обычно один бэкенд, одно приложение, один конвейер CI/CD и одно приложение, работающее в браузере. С модульным монолитом у нас может быть несколько бэкендов, но фронтенд по-прежнему является одним приложением с внутренними модулями, с одним конвейером CI/CD и одним приложением в браузере. С микрофронтендами каждый модуль становится своим собственным приложением со своим бэкендом и своим конвейером CI/CD. Наличие отдельного бэкенда для каждого микрофронтенда не является обязательным, но имеет смысл, потому что, делая это, мы уменьшаем связанность между модулями микрофронтендов, что является основной целью архитектуры микрофронтендов. Помимо границ между модулями, которые существуют в кодовой базе, основное отличие — это граница развертывания. Монолит и модульный монолит развертываются как единое целое. Микрофронтенды развертываются отдельно.
Так какие проблемы возникают у монолита в масштабе, которые побуждают команды переходить к архитектуре микрофронтендов? Три основные вещи. Во-первых, время сборки. В монолите каждое изменение вызывает полную пересборку приложения. В масштабе, с большой кодовой базой и множеством команд, это стоит реального времени и денег. Далее, связанность развертывания. Все команды используют один конвейер развертывания. Если одна команда не готова, все развертывание блокируется. Вы не можете выпустить, пока все части приложения не будут готовы. И технологическая зависимость. Все части приложения должны использовать один и тот же фреймворк и одни и те же инструменты сборки. Различные библиотеки — это нормально, но запускать React и Angular бок о бок или два менеджера состояний в одном приложении — это не очень практично. Иногда инструмент или библиотека, которые идеально подходят для одного модуля, не подходят для другого. Это очень редкий случай, и я никогда не видел такого на практике, но инструменты для этого существуют, что означает, что в действительно больших приложениях это может быть реальной проблемой.
Итак, если у нас есть монолит и мы рассматриваем переход к большей независимости, мы можем разбить решение на три вопроса. Важно также сказать, что микрофронтенды — это не единственное решение здесь, но мы берем их в качестве примера и нашей основной темы сегодня. Первый вопрос: медленные ли конвейеры CI/CD и часто ли они запускаются? Второй: работают ли несколько разработчиков над разными модулями в одном приложении, которые слабо связаны друг с другом? И третий: требуют ли разные функции разных инструментов? Если некоторые из них или все из них верны, именно тогда команды начинают рассматривать более модульный подход, например, микрофронтенды.
Итак, как выглядит фактический переход? Мы берем одно большое фронтенд-приложение, монолит, и разбиваем его на более мелкие приложения. Каждое приложение имеет свой код, свои инструменты, свои сборки и развертывания. Инструменты также могут быть общими между микрофронтендами, например, с помощью федерации модулей, но об этом мы поговорим позже. Все они по-прежнему могут находиться в одном репозитории, если это подходит команде. Одна команда может владеть одним или несколькими из этих микрофронтендов. Затем есть оболочечное приложение, тонкая обертка, которая обрабатывает маршрутизацию и макет. Оно загружает микрофронтенды и показывает их вместе как одну страницу, так что для конечного пользователя ничего не изменилось. Они по-прежнему видят то же веб-приложение. Они не знают и не заботятся о том, что оно состоит из отдельных частей. Итак, вот как это может выглядеть на практике. У вас может быть два микрофронтенда в одном репозитории. Мы называем их A и B, каждый принадлежит отдельным командам, каждый со своим CI-билдом. И третий микрофронтенд, мы называем его C, в отдельном репозитории со своей командой. Все три проходят через свой собственный конвейер CI/CD, и результаты объединяются в одно приложение.
На этом слайде я хотел показать, что возможны различные конфигурации. Один репозиторий для всех, один микрофронтенд на репозиторий и так далее. Но результат для конечного пользователя всегда один и тот же. Как решить, где проводить границы? Существует два распространенных подхода. Во-первых, по бизнес-домену. Каждый микрофронтенд — это продуктовая область, такая как оформление заказа, каталог или учетная запись пользователя. Это самый распространенный способ разделения. Во-вторых, по команде. Одна команда владеет одним или несколькими микрофронтендами. Если двум командам нужно изменить один и тот же микрофронтенд, граница, вероятно, находится в неправильном месте. На практике эти два обычно совпадают, потому что команды в любом случае организованы вокруг доменов.
Следующий вопрос: сколько микрофронтендов должно быть? Это зависит от того, как вы разбиваете приложение и почему. У нас есть четыре области на графике. Внизу слева — слишком мало микрофронтендов, или, по сути, все еще монолит. Итак, мы разбиваем приложение на недостаточное количество частей, когда на самом деле их должно быть больше. В этом случае мы получаем низкую сложность из-за небольшого количества развертываний и границ, но также и никаких преимуществ гибкости. Вверху слева — слишком много мелких микрофронтендов, которые все зависят друг от друга. Итак, мы получаем высокую сложность, но все еще низкую гибкость. Это худший случай. Вверху справа — приложение разбито на слишком много мелких частей, вероятно, больше, чем нам нужно. Правильный баланс находится внизу справа. Не слишком много частей, но и не слишком мало. Мы получаем относительно низкую сложность, потому что у нас нет слишком большого количества независимых частей для координации. Но в то же время мы получаем хорошую гибкость. Каждая часть достаточно велика, чтобы представлять собой значимую продуктовую область, и достаточно мала, чтобы одна команда могла полностью ею владеть.
Но что именно происходит, когда мы неправильно определяем размер? Если микрофронтенды слишком велики, мы получаем накладные расходы без преимуществ, потому что каждая отдельная часть означает больше сложности, которой нужно управлять. Несколько команд или разработчиков по-прежнему работают над одним и тем же микрофронтендом и продолжают блокировать друг друга. Те же проблемы, что и у монолита, но теперь с дополнительной инфраструктурой для обслуживания. Если микрофронтенды слишком малы, мы получаем две проблемы. Накладные расходы на инфраструктуру, потому что каждому микрофронтенду нужен свой конвейер сборки, развертывания, мониторинга, независимо от того, насколько он мал. И накладные расходы на связь, потому что слишком тонкое разделение создает слой координации между частями, которые принадлежат друг другу. Таким образом, в обоих случаях мы добавляем сложность, которой нет у монолита, но мы не получаем преимуществ архитектуры микрофронтендов или получаем их очень мало.
Теперь давайте поговорим об оболочке, иногда называемой хост-приложением. Это точка входа в приложение. Это не микрофронтенд сам по себе. Это обертка, которая объединяет все вместе. Оболочка обычно выполняет три функции. Во-первых, маршрутизация. Она решает, какие микрофронтенды загружать на основе текущего URL или других параметров. Поскольку микрофронтенды не всегда разделяются по URL, иногда у нас может быть два или более модуля на странице. Во-вторых, макет. Она отображает общие части страницы, такие как заголовок, нижний колонтитул и навигация, а также обрабатывает глобальные ошибки и загрузку. Она загружает пакеты микрофронтендов во время выполнения и монтирует их на страницу.
Оболочечное приложение нуждается в владельце. Вот где вступает в игру команда платформы. Команда платформы обычно не создает продуктовые функции. Они создают и поддерживают инфраструктуру, которую используют команды функций. Это включает в себя оболочечное приложение с его маршрутизацией, макетом и загрузкой микрофронтендов. Они также владеют общими библиотеками, такими как система дизайна, аутентификация и аналитика. Они настраивают конвейеры CI/CD, чтобы у всех микрофронтендов была необходимая им инфраструктура сборки и развертывания. И они заботятся об опыте разработчика. Это означает настройку локальной среды разработки, инструментов тестирования и документации. Вы можете думать о команде платформы как о команде, которая облегчает жизнь другим командам, предоставляя им всю необходимую инфраструктуру. На схеме мы видим, как обязанности распределяются между командами и ролями. На каждой странице у нас есть модуль, принадлежащий команде функций, и общие части, принадлежащие команде платформы. Таким образом, команды функций могут сосредоточиться на своих модулях.
Теперь давайте посмотрим, как микрофронтенды фактически объединяются, то есть как мы доставляем единое приложение пользователю из более мелких частей. Существует три основных подхода, и мы начнем с композиции во время сборки. При композиции во время сборки каждый микрофронтенд собирается и публикуется как пакет NPM. Таким образом, мы делаем это так же, как и с другими зависимостями в приложении. Это называется композицией во время сборки, потому что мы собираем и развертываем каждый микрофронтенд независимо как пакет NPM. Но затем, когда мы запускаем конвейер для всего приложения, мы объединяем их все вместе на этапе сборки. Каждый микрофронтенд собирается, публикуется в реестре, а затем оболочечное приложение устанавливает их все как зависимости и создает один окончательный пакет. Хорошая сторона этого подхода заключается в том, что настройка проста. Он работает с любым фреймворком, и нет сложности загрузки во время выполнения, потому что мы ничего не делаем во время выполнения. Наше приложение готово до того, как оно достигнет браузера пользователя. Недостаток в том, что когда мы обновляем один пакет, нам нужно пересобрать и переразвернуть все приложение. Это означает, что каждое изменение, даже исправление мелкой ошибки в одном микрофронтенде, вызывает полную сборку всего приложения. В масштабе с множеством микрофронтендов и частыми обновлениями это замедляет цикл выпуска и создает ту же узкую точку, которую мы пытаемся избежать. Где композиция во время сборки работает хорошо? Это хороший вариант для приложений, которые редко меняются, и пересборка при каждом изменении приемлема. Где следует избегать этого? Когда несколько команд выпускают независимо с разной скоростью, или когда выпуски достаточно часты, чтобы пересобирать все приложение каждый раз было медленно и дорого.
Второй подход — серверная композиция. Здесь каждый микрофронтенд — это отдельное серверное приложение. Это может быть сервер Node.js или бессерверная функция, что угодно, что генерирует HTML. Когда пользователь открывает страницу, каждое серверное приложение микрофронтенда генерирует свой фрагмент HTML и отправляет его оболочке. Оболочка вставляет их в шаблон страницы, и браузер получает полную страницу. Основное отличие от предыдущего подхода заключается в том, что на этапе сборки у нас нет полного приложения. Мы загружаем части динамически во время выполнения. Что вы получаете от этого подхода, так это более быстрое время до первого отображения. Пользователь видит контент быстрее, потому что сервер отправляет готовый HTML вместо пустой страницы, которую должен заполнить JavaScript. Это работает так же, как подход серверного рендеринга, и вы получаете независимые развертывания, потому что мы запрашиваем микрофронтенды каждый раз, когда пользователь открывает или обновляет страницу. Так что, когда одна команда развертывает новую версию, пользователи видят ее при следующей загрузке страницы без пересборки оболочки. Компромисс — более высокая стоимость инфраструктуры. Каждый микрофронтенд должен быть работающим приложением, а не просто статическими файлами. И связь между фрагментами сложнее. У вас может возникнуть ситуация, когда один фрагмент загружен, а другой нет, поэтому мы не должны делать их зависимыми друг от друга. Таким образом, мы можем сказать, что варианты использования этого подхода такие же, как и для серверного рендеринга в целом. Он не так хорошо работает, когда существует много взаимодействий между микрофронтендами. Фрагменты поступают с разных серверов, поэтому координация связи между ними сложнее, чем в клиентском подходе.
Третий подход — клиентская композиция. Здесь у нас все еще есть оболочечное приложение, но оно не устанавливает другие микрофронтенды как зависимости. Все микрофронтенды — это независимые модули. Отличие от серверного подхода заключается в том, что мы запускаем JavaScript на стороне клиента, который рендерит приложения для нас. Но по сравнению с классическим клиентским рендерингом, у нас есть независимые файлы JavaScript, которые рендерят независимые модули. Оболочечное приложение загружает эти пакеты и объединяет их в одну страницу непосредственно в браузере. Для клиентской композиции существует четыре основных варианта того, как мы можем это сделать. Во-первых, федерация модулей. Проще говоря, это плагин для микрофронтендов. Single-spa, пакет NPM, который запускает несколько фронтенд-приложений на странице. Iframe, встроенная функция браузера, которая встраивает веб-страницы в основную страницу. И веб-компоненты, еще одна встроенная функция браузера, которая позволяет создавать пользовательские HTML-элементы с инкапсулированными стилями и поведением.
Первый — федерация модулей. Она началась как плагин Webpack 5, но теперь она не зависит от сборщика с версии 2. Итак, как она работает? Каждый микрофронтенд объявляет, что он экспортирует, например, компоненты или функции, и что ему нужно от других микрофронтендов. Во время выполнения федерация модулей загружает пакеты и связывает их. Таким образом, один микрофронтенд может импортировать компонент из другого, как если бы это был локальный модуль. Что вы получаете, так это автоматическое дедуплицирование зависимостей. Это самая большая особенность федерации модулей, которая отличает ее от других подходов. Если два микрофронтенда используют React, например, загружается только одна копия библиотеки, и вы получаете прямые импорты между микрофронтендами. Таким образом, одно приложение может использовать компоненты из другого без какой-либо специальной настройки, кроме конфигурации. Компромисс — сложность настройки. Вам нужны знания сборщика для правильной настройки удаленных экспортов и общих зависимостей. И поскольку микрофронтенды могут совместно использовать компоненты и библиотеки друг с другом, это делает всю систему более хрупкой. Если что-то настроено неправильно, одна и та же библиотека загружается несколько раз или версии неожиданно меняются. Это может вызвать ошибки, которые трудно отследить. Федерация модулей хорошо работает для больших одностраничных приложений, где несколько команд часто выпускают функции и размер пакета имеет значение, потому что федерация модулей позволяет сделать его меньше, используя функцию общих зависимостей. Это также хороший вариант, когда команды используют один и тот же технологический стек и хотят независимых развертываний без дублирования общих библиотек. Где следует избегать этого, так это когда вам нужна максимальная изоляция между микрофронтендами. Поскольку все микрофронтенды используют одно и то же окно, одну и ту же глобальную область видимости и один и тот же DOM, один микрофронтенд может повлиять на другой. Так что, если изоляция важна для вашего варианта использования, федерация модулей не поможет с этим.
Следующий клиентский подход — single-spa. Это библиотека и пакет npm, который запускает несколько фронтенд-приложений на странице. Он монтирует и размонтирует их на основе URL. Он поддерживает три типа модулей. Во-первых, это приложения, основанные на маршрутах. Вы можете думать о них как об отдельных приложениях, которые single-spa показывает или скрывает в зависимости от текущего URL. И несколько приложений могут быть активны на одной странице одновременно. Во-вторых, парцеллы или компоненты. Это более мелкие части, которые могут быть встроены где угодно на странице внутри любого приложения. И в-третьих, утилиты — это общая логика, написанная на чистом JavaScript или TypeScript, доступная всем микрофронтендам независимо от того, какой фреймворк они используют. Таким образом, single-spa — это не просто способ разделения приложения на модули. Он предлагает высокоуровневую архитектуру для всей системы.
Теперь давайте посмотрим, что дает нам single-spa, а что нет. Single-spa не зависит от сборщика. Он работает с webpack, Vite, Rollup или вообще без сборщика. Федерация модулей теперь также не зависит от сборщика, но ей все равно требуется сборщик. Single-spa — нет. И каждое приложение собирает свои собственные зависимости. Так что, да, мы не получаем автоматического совместного использования, как в федерации модулей, но это также означает, что у нас более низкая связанность между модулями, что хорошо. Сбой библиотеки в одном микрофронтенде не влияет на другие. Общие зависимости по-прежнему возможны, но single-spa не обрабатывает их автоматически. Если вы хотите совместно использовать React между микрофронтендами, вам нужно вручную настроить карты импорта и внешние зависимости сборщика. И если вам нужен серверный рендеринг, я не уверен, возможно ли это, честно говоря, но если это так, это требует больше ручной работы, и у вас нет гарантии, что это будет работать так же хорошо, как с федерацией модулей. Так что, если у вас похожая настройка, пожалуйста, перепроверьте. Одной из функций, которую заявляют создатели single-spa, является то, что их библиотека особенно хороша для многофреймворковых установок, когда разные команды используют разные фреймворки, которые должны работать вместе на одной странице. А также для миграции фреймворков, когда вы постепенно заменяете один фреймворк другим, например, переходите от Angular к React. Применяется тот же анти-вариант использования, что и для федерации модулей. Если вам нужна максимальная изоляция, это не сработает, потому что оба подхода используют одно и то же окно, одну и ту же глобальную область видимости и один и тот же DOM.
Далее, iframe. Если вы не знакомы, iframe — это встроенная функция браузера, которая позволяет встраивать одну страницу внутрь другой. Каждый микрофронтенд — это отдельная страница, загруженная внутри тега iframe. Содержимое внутри iframe полностью изолировано от всего остального на странице. У него свой DOM, свой JavaScript-рантайм и свои CSS. Самое большое преимущество iframe — полная изоляция. Федерация модулей и single-spa обеспечивают изоляцию на уровне кода, но у нас все еще есть одно приложение, работающее в браузере. Iframe, поскольку это не просто библиотека, а функция браузера, позволяет нам полностью изолировать приложение на уровне браузера. Если один микрофронтенд выходит из строя, остальная часть страницы продолжает работать. Еще одно преимущество — простота настройки. Нам не нужны никакие дополнительные инструменты или конфигурации. Но есть и недостатки. Каждый iframe загружает свою собственную копию всех зависимостей, поэтому мы не можем совместно использовать библиотеки между ними, что увеличивает размер загрузки. Связь между iframe ограничена API postMessage, API браузера, который позволяет iframe обмениваться сообщениями друг с другом. Таким образом, мы не можем совместно использовать состояние или контекст между iframe напрямую, а также существуют проблемы с доступностью и SEO, потому что каждый iframe создает отдельный контекст просмотра внутри страницы, и программы чтения с экрана испытывают трудности с навигацией между ними. Iframe хорошо работают для встраивания сторонних приложений, таких как формы оплаты, виджеты чата или панели аналитики. Это также хороший вариант для загрузки устаревших приложений в новое без их переписывания. А также для соблюдения нормативных требований. Например, в банковской сфере или здравоохранении iframe может находиться на отдельном домене со своим собственным приложением, файлами cookie и политикой безопасности контента, и может обновляться независимо. Следует избегать iframe, когда микрофронтенды должны обмениваться данными, потому что нет общего состояния, общих функций и прямого доступа к DOM между iframe. И когда контент имеет динамическую высоту, потому что iframe не изменяют размер, чтобы соответствовать своему контенту, поэтому у вас есть полосы прокрутки внутри полос прокрутки. Таким образом, мы можем сделать вывод, что iframe — это не совсем инструмент для микрофронтендов, а скорее способ встраивания некоторых виджетов в существующее приложение.
И, наконец, веб-компоненты. Каждый микрофронтенд обернут как пользовательский HTML-элемент. Например, вместо обычного div вы создаете свой собственный тег, такой как my-checkout или my-dashboard. Оболочка использует эти теги в своем HTML, как и любой обычный элемент. Каждый элемент изолирован благодаря Shadow DOM, встроенной функции браузера, которая отделяет стили и скрипты микрофронтенда от остальной части страницы. Веб-компоненты работают с любым веб-приложением. Нам не нужен сборщик или какие-либо другие специфические библиотеки или инструменты. И они встроены в браузеры, поэтому библиотеки не требуются. Ограничение — это связь между микрофронтендами. Shadow DOM изолирует не только стили, но и события. Любое взаимодействие между микрофронтендами требует дополнительного кода. И передача данных ограничена. Веб-компоненты принимают только простые значения в качестве атрибутов, никаких сложных структур данных, таких как объекты или массивы. Веб-компоненты хорошо работают для сторонних приложений и для загрузки устаревших приложений без их переписывания. В отличие от iframe, веб-компоненты находятся внутри самой страницы, поэтому они выглядят и ощущаются как нативная часть приложения. Основной анти-вариант использования — когда вам требуется интенсивное взаимодействие между микрофронтендами. Shadow DOM блокирует передачу событий на родительскую страницу. Так что, если один микрофронтенд должен сообщить другому микрофронтенду, что что-то произошло, вам придется написать эту логику самостоятельно. И передача данных ограничена строками и числами в качестве атрибутов, поэтому мы не можем отправлять объекты или массивы.
Хорошо, теперь мы обсудили, что такое микрофронтенды, как они работают, и какие инструменты и подходы мы можем использовать для их реализации. Теперь давайте поговорим о том, почему, несмотря на огромный ажиотаж вокруг микрофронтендов, они не стали новым стандартом. Основная проблема, с которой сталкиваются команды, когда решают использовать микрофронтенды, заключается в том, что по умолчанию микрофронтенды не решают проблемы в вашем приложении, а добавляют множество новых проблем. Микрофронтенды не дают вам бесплатной гибкости. Они дают гибкость только тогда, когда организация и границы четко определены. Если эти условия не соблюдаются, микрофронтенды в основном преобразуют простую связанность в распределенную связанность. Таким образом, команды начинают с беспорядочного монолита. У них низкая гибкость, но у них по крайней мере низкая сложность инфраструктуры, потому что у нас есть одна единица развертывания. Когда команды решают перейти на микрофронтенды, они ожидают достичь независимых, слабо связанных микрофронтендов с высокой гибкостью. Но на практике многие команды оказываются с распределенным беспорядочным монолитом. Та же проблема, что и раньше, но теперь с огромными накладными расходами на инфраструктуру.
Итак, когда мы говорим о накладных расходах на микрофронтенды, что мы на самом деле имеем в виду? Первая проблема — связь. Когда вы разбиваете приложение на независимые части, эти части иногда должны обмениваться данными. Такие вещи, как состояние приложения или пользовательские предпочтения, не исчезают, потому что вы провели границу. Одним из распространенных решений являются пользовательские шины событий. Команды создают систему pub/sub для передачи данных между микрофронтендами, но это имеет тенденцию превращаться в собственный сложный слой. И еще одна проблема, связанная со связью, — синхронизация аутентификации. Когда пользователь входит в систему, он также должен быть авторизован во всех микрофронтендах, и то же самое с выходом из системы. Если это работает некорректно, пользователи видят, что некоторые части страницы авторизованы, а другие показывают экран входа.
Далее, сложность развертывания. С микрофронтендами каждый микрофронтенд получает свой собственный процесс CI/CD, свое собственное хранилище и свой собственный график выпуска. Это означает, что команды должны владеть и поддерживать все эти конвейеры. Следующая проблема — наблюдаемость. Когда что-то идет не так в настройке микрофронтендов, поиск причины означает проверку нескольких приложений, нескольких журналов и нескольких развертываний. Каждый микрофронтенд имеет свою собственную систему отслеживания ошибок, поэтому поиск источника сбоя означает проверку всех из них. И есть накладные расходы на координацию. Разделение приложения не устраняет необходимость в координации. Микрофронтенды по-прежнему должны общаться друг с другом. Это добавляет новый слой сложности поверх разделения.
Так когда же микрофронтенды действительно имеют смысл? Когда общий цикл выпуска фронтенда является реальным узким местом, и командам часто приходится ждать друг друга для развертывания. Когда ошибки приложения ведут себя как отдельные продукты, то есть это не просто функции, а продуктоподобные домены, каждый со своим собственным дорожной картой, приоритетами и жизненным циклом. Когда этими доменами владеют разные долгоживущие команды, которым нужна автономия в планировании и доставке.
И когда не следует использовать микрофронтенды? Прежде всего, микрофронтенды — это не стандартное решение. Микрофронтенды — это решение очень специфических проблем, и они нужны в очень небольшом количестве случаев. Но вот случаи, когда вы можете подумать, что вам следует использовать микрофронтенды, но, вероятно, это не лучшая идея. Например, когда кодовая база беспорядочна, микрофронтенды этого не исправят. У вас будет тот же беспорядочный код, но теперь он будет разделен между несколькими репозиториями и несколькими развертываниями, плюс к этому добавятся проблемы со связью. Вместо этого вам следует сначала очистить границы в существующей кодовой базе. Следующий анти-вариант использования — когда команды работают над разными частями продукта, но эти части тесно связаны. Если существует много общего состояния и много координации между ними, разделение вещей, которые должны работать вместе, создает больше проблем, чем решает. И третье, когда свобода технологического стека является основной целью. Это может выглядеть как преимущество того, что каждая команда выбирает свой собственный фреймворк, но на практике это то, что командам действительно нужно. Это сопряжено с реальными затратами, потому что вы теряете общие библиотеки компонентов и общие инструменты разработчика. Другой веб-фреймворк не должен быть целью. Это должно быть последнее средство, когда вы попробовали все остальное, и это все равно не работает.
Итак, если микрофронтенды не подходят большинству команд, что следует использовать вместо них? И ответ, вероятно, модульный монолит. Это одно приложение, которое разделено на четко определенные внутренние модули. Это по-прежнему фронтенд-приложение с одним рантаймом, одной сборкой и одним развертыванием. Отличие от простого монолита заключается в том, что внутри приложения домены, например, оформление заказа, продукты или учетная запись, рассматриваются как настоящие модули с границами, а не просто папки в дереве каталогов.
Давайте посмотрим, чем отличаются модульный монолит и микрофронтенды. С модульным монолитом у нас такая же структура команды и модулей, как и с микрофронтендами. Единственное отличие в том, что у нас нет отдельного процесса развертывания для каждого модуля. Таким образом, мы развертываем все приложение как единое целое. С микрофронтендами каждый модуль получает свой собственный конвейер CI/CD и развертывается отдельно. Конечный результат в браузере выглядит одинаково для пользователя. В обоих случаях у нас может быть команда платформы. Разница в том, что с настройкой модульного монолита нам не нужно поддерживать несколько развертываний.
Теперь самое важное в модульном монолите — это то, как вы определяете границы. Поскольку мы не устанавливаем физические границы между модулями, как в микрофронтендах с помощью федерации модулей, например, или других инструментов, нам нужно разделять модули на структурном уровне. Каждый модуль обычно представляет собой одну папку. Каждый модуль изолирован по умолчанию. Но как они могут общаться с другими модулями? Каждый модуль имеет файл index.js или .ts вверху, который действует как публичный API. Другие модули могут импортировать только из этого файла, а внутренние файлы остаются скрытыми. Файлы index иногда называют barrel files. Их чрезмерное использование может привести к проблемам с производительностью, но для определения границ модулей, я думаю, они работают хорошо, если сделаны правильно. Ключевое правило — экспортировать только небольшой набор вещей, которые другие модули могут использовать. Чего вы хотите избежать, так это экспортировать все, например, export everything from API или export everything from internal. Экспорт слишком большого количества не только приводит к проблемам с производительностью, но и сводит на нет цель модульного монолита. Потому что мы хотим держать модули изолированными по умолчанию и экспортировать наружу только то, что необходимо.
Как вы фактически обеспечиваете соблюдение этих границ? Во-первых, правила импорта. Другие модули могут импортировать только из публичной точки входа модуля, а не из внутренних файлов. Далее, правила зависимостей. Общие слои не должны зависеть от модулей функций. Направление зависимостей должно оставаться контролируемым. Владение срезами. Каждый модуль имеет владельцев, которые проверяют любые изменения в его публичном API. Дисциплина обзора кода. Импорты между модулями должны соответствовать контракту. Когда кто-то идет на уступки и импортирует внутренний файл напрямую, граница со временем начинает разрушаться.
На данном этапе мне нужно уточнить, что модульный монолит не заменяет архитектуру. Это не замена тому, как вы организуете код внутри приложения. Архитектура — это то, как мы организуем код внутри каждого модуля. Шаблоны, такие как дизайн срезов функций, вертикальные срезы или чистая архитектура. Модульный монолит описывает форму приложения в целом. Сколько модулей, каковы границы между ними, как они общаются. Это разные уровни, и иногда нам нужны оба. Но также, нам не нужен модульный монолит в каждом приложении. Я лично считаю, что модульный монолит хорошо сочетается с вертикальными срезами. Оба группируют код по бизнес-области, а не по техническому слою. Оба сохраняют связанный код вместе, что делает модули более легкими для понимания и владения. И оба следуют организационной структуре, потому что каждый модуль в модульном монолите представляет команду или проект внутри организации. У меня есть видео о фронтенд-архитектуре, такой как вертикальные срезы и другие шаблоны. Ссылки будут на экране и в описании. Таким образом, модульный монолит определяет высокоуровневое разделение приложения на модули. А вертикальные срезы определяют, как код организован внутри каждого модуля, обычно вокруг функций или вариантов использования. Итак, это схема того, как они могут работать вместе. Слева, продуктовый модуль, принадлежащий команде А, разделен на три вертикальных среза: список продуктов, карточка продукта и поиск продукта. Каждый срез проходит через все три слоя: представление, бизнес-логика и доступ к данным, которые являются типичными слоями для многоуровневой архитектуры. Справа, модуль корзины, принадлежащий команде B, имеет свои собственные срезы: корзина, оформление заказа и доставка. И та же структура здесь. Внизу находится глобальный слой с общей навигацией и пакетами, принадлежащими команде платформы. Каждый модуль имеет свой собственный файл index, который экспортирует общедоступные части. И это работает как контракт между этим модулем и другими модулями и оболочечным приложением.
Позвольте мне прояснить, чего не дает модульный монолит. Он не изолирует CSS по умолчанию. Все модули по-прежнему находятся в одном фронтенд-рантайме, поэтому правила стилизации по-прежнему могут влиять друг на друга. Конечно, вы можете изолировать его, но модульный монолит ничего особенного для изоляции стилей для вас не делает. Он не позволяет использовать разные фреймворки. Приложение по-прежнему работает на общем стеке платформы. Он не позволяет развертывать модули независимо. И, как вы помните, микрофронтенды позволяют это делать. И он не обеспечивает изоляцию во время выполнения. Если что-то выходит из строя во время выполнения, все модули являются частью одного и того же приложения, поэтому все они могут выйти из строя, если мы не настроим надлежащие границы ошибок. Но вот что он делает. Он структурирует приложение по домену, например, оформление заказа, продукты, учетная запись, которые все должны быть независимыми и изолированными друг от друга. Таким образом, разные команды могут работать над ними независимо. Он определяет четкие границы. Модули экспортируют публичный API и скрывают свои внутренние файлы. Он контролирует зависимости. Модули зависят друг от друга только через разрешенные точки входа, что сначала создает строгий контракт между ними, а во-вторых, делает эти зависимости видимыми, потому что они находятся в одном месте. Так что мы знаем, что от чего зависит. И он также поддерживает владение командами. Команды могут владеть модулями, не разделяя приложение на отдельные рантаймы или создавая несколько развертываний. И во многих случаях именно это командам на самом деле нужно.
Теперь давайте поговорим о том, что изменило разговор об архитектуре за последний год: инструменты кодирования на базе ИИ. Причина, по которой я решил добавить этот раздел, заключается в том, что я считаю, что ИИ изменил то, как мы должны думать и использовать микрофронтенды. Но прежде чем мы начнем, я хотел бы поговорить о принципе, который очень помогает при работе с ИИ. Этот принцип называется "мусор на входе, мусор на выходе". Если наша кодовая база беспорядочна, если границы нечеткие, а зависимости идут во всех направлениях, то инструменты ИИ будут давать беспорядочные результаты. Неважно, какой инструмент вы используете. Качество того, что производит LLM, зависит от качества контекста, который он получает. Вот почему я считаю, что мы должны заботиться об архитектуре еще больше сегодня, чем раньше. Чтобы понять, как микрофронтенды или модульный монолит могут помочь нам писать лучший код с помощью ИИ, нам нужно понять одну концепцию. Она называется контекстное окно. Контекстное окно — это общее количество текста, такого как код, инструкции, разговоры, которое модель ИИ может обработать за один раз. Каждая модель имеет жесткий предел. Текущие модели варьируются примерно от 200 000 до 2 миллионов токенов. Очень грубо, это от 150 000 до 1,5 миллиона строк кода. И вот что не так очевидно. Большее контекстное окно не всегда означает лучшие результаты. В какой-то момент, чем больше информации вы даете модели, тем хуже она работает при поиске конкретных деталей. Слишком мало контекста тоже нехорошо, но слишком много — так же плохо. Проблема называется "потеря в середине". LLM уделяют больше внимания информации, которая находится в конце их контекстного окна. Информация в середине получает меньше внимания. И если вы подумаете об этом на секунду, это очень похоже на то, как люди лучше запоминают начало и конец долгого совещания, чем то, что было сказано в середине. Таким образом, мы можем видеть, как это работает на этой картинке. Высокое внимание в начале, падение в середине, снова рост в конце. Оранжевая точка отмечает самую низкую точку. Информация, помещенная туда, оказывает наименьшее влияние на результат.
Так что это значит для нас на практике? По мере продолжения разговора контекстное окно смещается. Наибольшее внимание уделяется последним сообщениям и системным инструкциям в начале. Выделенная часть, контекст приложения, — это то, что нас волнует. Это код и информация о проекте, которые модель использует для генерации своего ответа. Таким образом, чем меньше шума в контекстном окне, тем лучше результаты.
Как это связано с архитектурой? На изображении мы видим три сценария. Монолит, где ИИ должен работать с одной большой кодовой базой, где границы нечеткие, а контекст слишком велик. Поскольку ИИ не знает, что релевантно, а что нет, ему приходится проверять все, и, конечно, у него больше шансов случайно что-то сломать. Второй сценарий — распределенный массивный монолит, где мы разбиваем на микрофронтенды, но модули по-прежнему связаны друг с другом. Контекст все еще слишком велик. Так что у нас та же проблема, но в немного другой конфигурации. И первый сценарий, когда, независимо от того, используем ли мы модульный монолит или микрофронтенды, границы между модулями четкие. Таким образом, инструмент ИИ получает наименьший возможный контекст среди этих трех вариантов, что позволяет ему сосредоточиться на одном модуле. Таким образом, как мы видим, и микрофронтенды, и модульные монолиты могут улучшить разработку с помощью LLM, но только при условии, что модули действительно и должным образом изолированы. Когда это так, LLM работает с меньшим контекстом, ограниченным модулем. Он более эффективно использует контекстное окно и вносит изменения с меньшим риском, потому что работает в четких границах.
Есть и другой аспект. Четкие границы модулей облегчают параллельную работу нескольких агентов LLM, поскольку каждый агент работает над разным модулем с меньшим риском наложения или конфликтов. Таким образом, на изображении мы видим, как три агента, по одному на модуль, работают одновременно. Это работает только в том случае, если модули действительно независимы. Так что, если это не так, агенты начнут изменять одни и те же файлы и создавать конфликты.
Так что же все это значит для нас? До LLM модульность в основном помогала командам работать параллельно. С LLM модульность также помогает разработчикам выполнять параллельную работу через агентов. И причина проста. Меньшие и лучше изолированные модули дают агентам меньший рабочий контекст и оставляют больше места в контекстном окне для полезной информации. Но вам не нужно использовать микрофронтенды для этого. Модульный монолит с четкими границами дает те же преимущества инструментам ИИ без распределенных накладных расходов на инфраструктуру.
Итак, главный вопрос видео: следует ли нам использовать микрофронтенды или нет? Мы начинаем с монолита. Затем мы задаем себе вопрос. Имеет ли наше приложение несколько доменов? Если нет, мы должны пока оставаться на монолите. Если да, четкие ли границы между этими модулями? Если нет, мы должны сначала их уточнить. Мы не должны добавлять инфраструктуру микрофронтендов поверх нечетких границ. Если границы четкие, мы должны сначала понять, будет ли достаточно использовать модульный монолит. Чтобы понять это, нам нужно спросить себя, действительно ли нам нужно развертывать модули независимо? Если нет, мы должны оставаться на модульном монолите. Если вам действительно нужны независимые развертывания, есть ли у вас владение платформой и зрелость для ее поддержки? Если нет, пока оставайтесь на модульном монолите. Только если у вас есть четкие границы, реальная потребность в независимом развертывании и команда платформы для ее поддержки, тогда микрофронтенды имеют смысл.
Если вы хотите углубиться в архитектуру фронтенда, у меня есть две ссылки для вас. Вот видео обо всех архитектурных шаблонах и о том, как применять их к фронтенд-приложениям. А вот текстовое руководство по шаблонам архитектуры фронтенда, недавно обновленное большим количеством шаблонов и информацией о том, когда что использовать. Ссылки на оба будут в описании.
На этом все на сегодня. Надеюсь, это было полезно. И прежде чем вы уйдете, создание этих видео занимает много времени, поэтому, если вы можете поддержать меня лайком и подпиской на канал, это будет очень полезно. И увидимся в следующем. Пока.