Pull to refresh

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 и всех подпроектах с колоссальными усилиями и увольнением разработчиков, которые вынуждены возиться в подобном.


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

Виталий, а расскажите все же, где у вас микрофронтенды, а не модульный монолит?

Хотелось бы увидеть кейсы:

  1. Встраивание в реакт приложение extJS приложения

  2. Встраивание в реакт приложение angular/vue микрофронтенда

  3. Добавление любого вашего микрофронтенда в виде виджетов куда либо.

Здравствуйте. В статье руководствовался таким пониманием микрофронтендов:

"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 конечно же при необходимости можно было и на реакт, и на ангуляр. Но мы таким идиотизмом не занимались)

Можно ли назвать такой подход «микрофронтендами»? хотя такого выражения тогда ещё вроде не было..))

Sign up to leave a comment.