Comments 10
Редко появляются технические статьи из фронтового энтерпрайза, интересно почитать. Первые две схемы действительно полны недостатков, третья — выглядит удобнее, но возиться с ее настройкой ввиду новизны приходится немало. Насчет стрелочных функций в IE, кстати, так как модули — отдельно собранные файлы, то при их загрузке код можно выполнить в try-catch с отслеживанием превышения времени выполнения в секунды 3 (для защиты от бесконечных циклов) и логировать случаи жестких ошибок или некорректного синтаксиса. Если уж совсем независимые команды делают, то это дополнит защиту ErrorBoundary.
В целом про микрофронты можно написать много нехорошего, и приведенные проблемы здесь — вершина айсберга, под каждой строчкой в тексте виден опыт страданий, проб и ошибок, а то, что получилось в итоге все равно имеет большую сложность, несмотря на ужимание концепта микрофронтов до "монобиблиотека + моносборщик + монофреймворк", просто с разнесением по нескольким репозиториям с условно-независимым циклом поставок. Больно было читать про сотни граблей и упорную борьбу с недостатками каждого подхода, чтобы всего лишь распараллелить работу команд, использующих одинаковый стек.
При этом в монорепе:
"Команды разрабатывают модули изолированно и не влияют друг на друга" — если страницы сделаны через вебпаковые асинхронные импорты, то проблемы нет. В каждой папке страницы может быть много модульных сторов, экшенов, компонентов, роутов, апи-запросов — и для разработки сложных страниц не нужно лезть в окружающий код, при необходимости изоляцию (запрет глобальных импортов) можно включить в eslint-правила. Не так уж сложно проконтролировать то, что "команда не может внести изменения в архитектуру системы" — а с точки зрения безопасности, если захотят, то и через iframe, npm или module federation встроят любой код типа картинки, делающей гет-запрос с шифрованным auth-токеном и базой юзеров. Микрофронты тут никак не защищают, и вообще встраивать код быстро меняющихся чужих команд которые пишут как хотят внутри своих реп — никак не по энтерпрайзу.
"Изолированность позволяет каждой команде работать в привычном режиме" — в монорепе вполне можно сделать удобные релизные ветки и менеджерить общие релизы в мастер. Есть проблема с откатами, но с микрофронтами там тоже все непросто.
"Команды придерживаются отличающихся практик и требований к написанию" — если так уж нужно, можно отключить для определенных папок-страниц проверки ESLint и даже TS, если некая аутсорс команда не хочет автоформатирования, типизации, проверки ошибок в рантайме и ей обязательно нужны табы вместо пробелов для отступов и длинные строки инструкций.
"Бизнес-процессы в некоторых модулях очень сложны" — никак не связано с микрофронтендами, почему написано в списке причин их заводить — непонятно.
Итого — вместо менеджеринга фиче- и релизных веток в монорепе было решено пойти во все тяжкие, потратив большое количество ресурсов компании и получив все возможные "болячки" микрофронтов, о которых уже много было написано. При этом при выделении в монорепе просто изолированных папок-модулей и при асинхронной их подгрузке, не было бы проблем с кешем, полифиллингом (в микрофронтах полифиллы дублируются, т.к. не весь код прогоняется через корневой бандлер), передачей глобальных данных (сторов) и методов (контекст, пропсы), расхождением или дублированием версий uikit и других библиотек от модуля к модулю, дублированием, выделением общих сложных компонентов (типа панели выбора сотрудника), настройкой ci/cd, локальной подгрузкой актуальных модулей (т.к. есть общий master), возней с версионированием и откатом npm-пакетов, типизацией, таких проблем с безопасностью этих "черных ящиков"...
Не проще ли выбрать пару понятных проблем монореп — необходимости аккуратного менеджеринга веток разными командами и оптимизации первичной сборки и пересборки, вместо всего набора этих и многих других проблем (особенно если вдруг станут не моностековые микрофронты)?
Здравствуйте. Спасибо за развёрнутый комментарий!
Возможно, я недостаточно акцентировал внимание на том, что проект разрабатывают совсем независимые команды. Некоторые команды — это внутренние команды X5, а некоторые — подрядчики. Иногда о командах мы знаем только ссылку на встраивание модуля) Поэтому не очень хотелось бы предоставлять доступ к коду совсем неизвестным людям.
К вопросам о разделении правил линтера в монорепозитории — это всё так, но, на мой взгляд, это может работать только в случае какой-то слаженной работы команд. Как минимум нужно договориться о том, что все команды обязательно используют линтер и не делают того, что он не разрешает. К сожалению, сейчас даже это не всегда соблюдается. Это приводит к проблеме, что в случае с монорепозиторием каждый MR в dev каждой команды должен проходить ревью. Сейчас мы встраиваем больше десяти модулей, и кажется, что это привело бы к ещё большим временным затратам.
"C точки зрения безопасности, если захотят, то и через iframe, npm или module federation встроят любой код" — чтобы это сделать, должно быть намерение всё сломать, но мы всё же полагаемся на адекватность команд, а защита направлена скорее на случайные изменения в коде, которые очень легко проглядеть.
Также в случае с монорепозиторием мы должны тратить свои ресурсы на ревью сетапа каждого модуля, потому что для этого нужно внести изменения и в лерну и в корневой package.json. Да и опять же: хочется контролировать, что находится в своём проекте.
Можно было бы организовать код в виде сабмодулей, но в таком случае это бы ничем принципиально не отличалось от текущего подхода. Всё равно приходилось бы публиковать uikit и core, и выстраивать последовательность подключения. Но в таком случае невозможно делать релизы модулей независимо от основного проекта, а это основная причина, по которой мы отказались от второго варианта с NPM-пакетами. Это сильно растягивает процессы.
В общем, основная мысль такая: согласен, что работать в некоторых моментах с монорепозиторием было бы проще, но в случае со множеством совсем независимых команд это несёт много рисков.
"В тексте виден опыт страданий, проб и ошибок" — возможно, сложилось такое впечатление от того, что некоторые моменты расписаны слишком подробно. В действительности не всё так плохо) Даже на сетап федерации модулей суммарно было затрачено несколько дней рабочего времени и потом ещё несколько раз были небольшие доработки. За полгода эта технология сэкономила гораздо больше ресурсов, чем мы на неё потратили.
Так и нужно было говорить — что использование микрофронтендов введено из-за бизнес-ограничений в виде участия многих независимых команд. А то, что написано в статье под строками "о микрофронтендах много говорят, в нашем случае этот подход полностью оправдан и необходим" с перечислением "изолированность, безопасность, зоопарк, параллельность поставок" — не является причиной. Бизнесу-то может и можно их продать, я видел команды, тратившие громадные ресурсы, чтобы попробовать подобный концепт, хотя не было никакой необходимости — и проекты уходили в глубокое сильно запутанное легаси, в котором даже редизайн кнопки мог занять месяц, не говоря о более сложном и связанном функционале. Поэтому продать микрофронты технарям под соусом "параллельности и свободы практик" не получится — очевидно, что количество проблем вскоре превысит все мыслимые пределы.
Независимость релизов от core-обертки иногда благо, иногда — трагедия. Тоже были случаи, когда в core были breaking changes и надо было все микрофронты обновить (и не раз такие случаи), и если бы в одной репе это выглядело бы в основном как "пробежался автозаменой — проверил — выложил", то при многих командах выглядело как "фриз core -> ждешь неделю апдейта от 3 команд -> фриз этих 3 команд -> ждешь 2 недели остальные команды либо лезешь сам разбираться в их зоопарке -> общий тест -> еще несколько правок, пока все команды зафрижены -> релиз". Сколько тут ресурсов и времени разработчиков терялось — не сосчитать, а опыт взят из богатой конторы с большим количеством фронтов. Ошибка была только одна — решили делать микрофронтенды. Таких историй у меня выше крыши, но статьи по ним не пишут)
Если и не полный фриз а команды продолжают работу пока другие подтягиваются — то в момент когда все должны сойтись в одной точке прилетают критичные баги по несвязанным задачам сразу из нескольких микрофронтов + неготовность некоторых сервисов бэка под новые изменения, что может привести вплоть до отката breaking changes в core и всех подпроектах с колоссальными усилиями и увольнением разработчиков, которые вынуждены возиться в подобном.
В вашем случае, когда проект не слишком сложный, монофреймворковый, без существенного функционала в обертке и при достаточном количестве ресурсов на техподдержку — то скорее всего не сталкивались с этим, как и с сотнями других, которые уже выстраданы в других компаниях, но судя по статье, все-таки натыкались на определенное количество. Если так устроена бизнес-модель, то ничего не поделать, кроме как посочувствовать, но не стоит говорить, будто это осознанный выбор — не затягивайте в этот мир рассинхрона и зоопарка других разработчиков.
Виталий, а расскажите все же, где у вас микрофронтенды, а не модульный монолит?
Хотелось бы увидеть кейсы:
Встраивание в реакт приложение extJS приложения
Встраивание в реакт приложение angular/vue микрофронтенда
Добавление любого вашего микрофронтенда в виде виджетов куда либо.
Здравствуйте. В статье руководствовался таким пониманием микрофронтендов:
"The idea behind Micro Frontends is to think about a website or web app as a composition of features which are owned by independent teams. Each team has a distinct area of business or mission it cares about and specialises in. A team is cross functional and develops its features end-to-end, from database to user interface." micro-frontends.org
В нашем случае микрофронтендами являются модули, которые разрабатывают независимые команды. Каждая команда разрабатыват раздел, описывающий какой-то бизнес-процесс. Она ведёт независимую разработку в отдельном репозитории, и их модуль загружается уже в рантайме, когда пользователь открыл страницу.
Спасибо за столь развернутую статью. А как быть с версиями отдельных модулей при использовании 3-го метода? Или вам всегда важен только последний релиз?
Здравствуйте. Да, нам важен только последний релиз. Если требуется подменить модуль на более старую версию, достаточно нажать кнопку "деплой" у нужного пайплайна.
Если появится еще один корневой проект, например lk3, где должна использоваться иная версия модуля child_project чем в lk2, то есть ли варианты реализации для поддержки модулей с версиями с помощью Module Federation?
Да, в таком случае можно пойти несколькими путями.
Самый простой: немного доработать деплой Module Federation, чтобы путь к модулю содержал его мажорную версию и просто подключить эти модули в ЛК как два независимых модуля, т.к. воспринимать их не как две версии одного, а как два разных компонента. И просто настройить в панели администратора правила того, какой из модулей на какую группу пользователей / домен показывать.
В случае, если появится что-то совсем экзотичное, то можно просто собрать новый lk3 на основе core и модулей.
В общем, такой сценарий возможен и реализуем после небольшой аналитики)
Пожалуйста, делитесь в комментариях, какими способами вы решали похожие задачи
Года три-четыре назад делал приложение типа CRM-ки. Визуально была боковушка-сайдбар с кучей менюшек, и центральная контентная часть. Бекенд на symfony. Сверстал базовый шаблон с сайдбаром в серверном "twig-шаблонизаторе". Т.е. сайдбар-меню генерировалось на сервере. Каждый пункт меню — это ссылка, которая вела на отдельный серверный роут. При этом контентная часть была пустым дивом, в который уже грузилось мини-SPA приложение (react) в соответствии с текущим серверным роутом (пунктом меню). Т. е. на каждый серверный роут был отдельный twig-овский шаблон (унаследованный от базового) со своим отдельным подключёнными js- и css- бандлами, актуальными для данного пункта меню (роута). При достаточно функционально огромном приложении — получился довольно лёгковесный фронт, максимум 1,1мб на роут (там где графики всякие).
примерный код был такой:
{% extends 'base-layout.html.twig' %}
{% block title %}
Traffic controller
{% endblock %}
{% block stylesheets %}
{{ parent() }}
<link rel="stylesheet" href="{{ asset('css/traffic-controller/index.min.css') }}">
{% endblock %}
{% block content %}
<div class="app__content" id="root">
<span>загрузка ...</span>
</div>
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script>
window.__INITIAL_STATE__ = {
// какие-то уже готовые начальные поля для данного spa
// полученные при серверном(!) рендеренге этого шаблона
user: {{ user }}
};
</script>
<script src="{{ asset('js/traffic-controller/index.min.js') }}"></script>
{% endblock %}
Соответствующий индексный js:
// js/traffic-controller/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from 'js/traffic-controller/App.jsx';
const rootNode = document.getElementById('root');
ReactDOM.render(<App />, rootNode);
«Ядро» (наверное, лучше называть обвязка) — это был только сайдбар с щепоткой нативного js, который отвечал за скрытие/раскрытие пунктов меню, и за кнопку-гамбургер на мобилках. Функционал этих мини-spa которые живут на разных серверных роутах сильно не пересекался. Максимум редирект с одного spa в другое.
Писать эти мини-spa конечно же при необходимости можно было и на реакт, и на ангуляр. Но мы таким идиотизмом не занимались)
Можно ли назвать такой подход «микрофронтендами»? хотя такого выражения тогда ещё вроде не было..))
Пятёрочка — Интегрируй меня полностью