Всем доброго времени суток!
Сегодня речь пойдёт о таком страшном звере, как micro-frontend. Знаю: всем эта тема порядком надоела, но просмотрев полтора десятка выступлений осознал, что не все до конца понимают, что это такое и какие сложности следует решать при организации micro-frontend’а. В данной статье я дам универсальные советы, расскажу с чем не стоит связываться и, в целом, как не превратить проект в ад из callback’ов или непонятных интерфейсов. Итак, обо всем по порядку.
Что такое micro-frontend?
Точного определения днём с огнём не сыщешь. Суть в том, что термин micro-frontend подразумевает наличие множества изолированных приложений с интерфейсом, для взаимодействия с ними посредством API. Это позволяет использовать версионированность, не опираясь на рядом стоящие компоненты. Наглядным примером такой реализации являются различные npm-пакеты. Так же micro-frontend подразумевает под собой использование микро-сервисной архитектуры, что в совокупности даёт нам инкапсулированную логику не зависящую от окружения.
Чем был плох предыдущий подход - монолит?
Для того, чтобы понять преимущества micro-frontend’а нам следует разобраться чем именно он отличается от, так называемого, монолита.
Монолитное приложение характеризуется высокой связанностью частей системы. С одной стороны, уменьшает затраты на архитектуру и повышает скорость разработки, с другой - увеличивает стоимость разработки нового функционала, его изменения и поддержки. Из вышеизложенного следует, что логика может быть размазана по приложению и, как следствие, может свести к минимуму переиспользуемость компонентов.
Я прямо слышу крики: “ Что за ересь?!”. Давайте разберемся поподробнее, что же я имею в виду. Для начала задам пару наводящих вопросов для определения говорим ли мы об одном и том же.
Можно ли взять какой-то модуль и использовать его в другой системе, при этом не занимаясь его адаптацией?
Этот модуль зависит от экосистемы (стека) окружающего его приложения?
Вот вам и ответ на вопрос: в чем же минус монолита. Это совершенно не обозначает того, что он плох. Это значит только то, что вами не корректно выбран инструмент для реализации конкретной задачи. Вы же гаечным ключом не забиваете гвозди, верно? Тут ровно та же ситуация - нужно понимать что и для чего вы используете.
Плюсы micro-frontend’а
Теперь разберём, что нам дает использование micro-frontend’а и в каких ситуациях его следует применять. Для начала предлагаю определиться для каких проектов он актуален: на мой взгляд, это проекты выше среднего. В таких проектах, как правило, более одной команды и более десяти разработчиков frontend’а. Цифры условные, но наглядные. Если с размером все понятно, то предлагаю перейти к плюсам, которыми нас одаривает micro-frontend.
Модульность - первоочередное преимущество, позволяющее спокойно перемешивать модули между собой, не задумываясь об их работоспособности.
Версионность - преимущество двоякое и мы еще вернёмся к нему в минусах. Если и говорить об этом как о преимуществе, то оно позволяет точно знать с какой версией модуля мы работаем на проекте в данный момент времени и как им управлять.
Инкапсулируемость – не путайте с модульностью. Да, модули тоже могут быть зависимыми, но инкапсулируемость дает нам изолируемость всей логики в модули, которая не зависит от внешних факторов.
API - так как вся логика приложения у нас инкапсулирована, а нам как-то надо связываться с приложением, то для управления используют API. Оно отвечает за обмен данными с модулем, а также его состоянием.
Документация - без документации micro-frontend вообще невозможен. Как было сказано выше, вся логика инкапсулирована в модуль, а управление данными, состояниями и прочим происходит только при помощи API. Выходит, что без документации или хотя бы типизации мы потратим уйму времени разбираясь как управлять нашим фронтом.
Тесты - тесты обязательный атрибут micro-frontend’а. Они позволяют контролировать не только качество работы, но и в целом работоспособность данного модуля.
Быстродействие - так как модули не нагружены лишней логикой, то в них содержится только то, что необходимо для работы, что обеспечивает наилучшую оптимизацию кода.
Масштабируемость команды – как известно, мы работаем для бизнеса. Иногда у этого самого бизнеса возникает потребность ускориться в разработке, и тогда приходят с вопросом «как это сделать?». Очевидно, потребуются люди, их организация, а также обеспечение сотрудников загрузкой и отслеживание производительности. Модульная система как раз позволяет каждой команде спокойно работать над частью своего кода и, при этом, не мешать соседу.
Масштабируемость приложения - так как у нас все состоит из кирпичиков, то присутствует возможность построить стену необходимой длины.
Скорость деплоя - поскольку все построено на модулях, не зависящих друг от друга, и их размер гораздо меньше размера готового приложения, то деплой можно запустить в несколько потоков (по отдельным модулям). Некоторые модули могут переиспользоваться не один раз, что также предоставит профит, не требующий дополнительных комментариев.
Релиз частями - несколько сомнительное преимущество, но тем не менее позволяет отправлять в релиз части системы по отдельности. Удобно, когда много команд и у всех плавающие спринты и производительность.
Как видите у micro-frontend’а полно плюсов, хотя это как в медицине: чем больше плюсов, тем больше побочных эффектов. Для полноты картины о них надо знать и понимать, а потому переходим к минусам.
Минусы micro-frontend’а
Как известно, технологий без минусов не бывает. Точнее бывает, но только в России. Micro-frontend это не обошло стороной: минусов много, и они достаточно весомые, о чем я и расскажу ниже. С частью я знаю, как бороться, а с частью, к сожалению, нет.
Архитектура - модули должны содержать в себе инкапсулированную логику. Это делает планирование не совсем тривиальной задачей, что в свою очередь повышает трудозатраты на разработку.
Тестирование – тестировать придется всё и вся. Кроме обычных тестов потребуются еще интеграционные, так как связь между модулями осуществляется исключительно по API.
Версионность – вот теперь давайте поговорим об этом, как о минусе. Вам придется точно понимать и отслеживать, что и в какой версии сделано. Это значит, что потребуется карта зависимостей, которая будет с завидной регулярностью стрелять вам в ногу.
Сборка релиза - еще одна головная боль, приводящая в ярость релиз-менеджеров и заставляющая приносить жертвы в полнолуние, так как постоянно приходиться сверять версии модулей, которые требуются в сборке, а также их зависимости.
Документация - держите её в актуальном состоянии постоянно, ведь от нее зависят соседние команды. Я серьёзно. Упаси вас Бог что-то сделать и не задокументировать это - вас сожгут, как еретика, при первом же релизе сторонней команды.
Дебаггинг - очередной вид забав, заставляющих плавить стулья и призывать дьявола с требованием забрать вашу душу в обмен на решение бага. То, что происходит за пределами вашего модуля для вас, как правило, большая черная дыра, из которой периодически к вам будут прилетать порции ошибок.
Производительность - может я для кого-то сейчас открою тайну, но скорость быстродействия micro-frontend’а, как правило ниже, чем у монолита. Это связано с тем, что не всегда понятно что можно кэшировать, а что категорически нельзя. Следует грамотно выбирать стратегию кэширования и обновлений, так как браузер тоже будет подкидывать дровишек в ваш, и без того не маленький, костер пригорания. Плюс ко всему объемы перегоняемых данных из одного модуля в другой, гораздо выше.
Избыточность - каждый из модулей должен содержать в себе всё, что ему необходимо для работы. Да, абсолютно всё равно, что в соседнем модуле есть точно тоже самое. Micro-frontend должен быть атомарным и мочь функционировать в гордом одиночестве.
SEO - вишенка на торте. Мы же ещё помним о том, что мы работаем на бизнес, а залог успеха бизнеса — это продвижение? Соответственно следует продумать как именно у вас будет работать SSR (Server Side Rendering - он же серверный рендеринг страниц для уменьшения нагрузки на браузер), как проставлять теги и многое другое. То самое, за что вас будут парафинить ваши коллеги, занимающиеся этим самым продвижением.
Далее поделюсь информацией как можно бороться с частью из этих минусов, на что стоит обратить внимание и как продумывать свои действия наперед.
Путь наверх
Как поется в одной песне “Не пройдя преисподней, Вам не выстроить рай!”. Ещё до начала работ вам следует решить все организационные вопросы, спланировать кто, чем будет заниматься и в какой последовательности. Проще говоря, Вам нужна дорожная карта хотя бы на полгода.
Обеспечить работу всех в одном репозитории - вы не ослышались. Лучшее что вы можете сделать - использовать монорепозиторий. Уже вижу летящие в меня камни, но предлагаю не торопиться с выводами.
Для облегчения вашей участи есть прекрасный инструмент - NX (https://nx.dev/). Он позволяет разбить монорепозиторий на библиотеки, согласно тем модулям, которые будут содержаться в готовом приложении. Также NX позволяет ускорить локальную пересборку проекта, за счёт инкрементального обновления, вести версионированние, и при необходимости генерировать npm-пакеты с нужными зависимостями. Инструмент содержит в себе storybook, который будет просто необходим для библиотеки компонентов и тестирования готового результата. Доставив плагины, вы всегда сможете посмотреть, как реагирует модуль в связке или в вакууме, а во избежание циркулярных зависимостей воспользуйтесь функцией построения графиков.
Микросервисы - полноценный micro-frontend не может существовать без микросервисной архитектуры. Чтобы облегчить её создание, предлагаю использовать Nest (https://nestjs.com/), как принято про него говорить: Express на максималках. Вам так или иначе придется создавать слой абстракции (BFF - он же Back for Front), чтобы модули ходили через него на бек. Nest позволяет стандартизировать все запросы к беку, а также настроить кастомную генерацию ошибок. Nest умеет нормально обрабатывать DTO, которые потом спокойно съедает swagger, которые вы сможете прикрепить к storybook, что даст вам почти идеальную документацию для работы с модулями.
Настройка окружения - под этим я подразумеваю настройку линтера, внедрения TS, настройку прикомитных хуков. Причем прикомитные куки должны быть настроены таким образом, чтобы они запрещали вообще комит, если ругается линтер, или в какой-то момент не проходят тесты. Это убережет вас от говно-кода, а также позволит следить за качеством кода в целом.
Качество кода - для поддержания качества кода вам потребуется какой-либо анализатор кода. Рекомендую использовать Sonar (https://www.sonarqube.org/). Данный инструмент бесплатен, а также имеет готовые плагины под все популярные IDE. Sonar умеет определять дубляж кода, давать рекомендации по упрощению алгоритмов и много чего еще, что позволит хоть как-то разгрузить и без того забитую голову. Его тоже можно включить на прогон прекомитов.
Отслеживание нежданчиков - Я думаю все в курсе, что иногда на фронте бывают такие юзер-кейсы, которые не придут ни одному разработчику или тестировщику в трезвом уме и здравой памяти в голову. Для таких случаев рекомендую внедрить в проект Sentry (https://sentry.io/). Он позволяет отслеживать действия пользователей, приведших к ошибкам, фиксировать их или показывать с каких устройств пользователи осуществляют вход. Так же в нем достаточно много всяких метрик, на которые вы можете положиться. Метрики разные: начиная от производительности и заканчивая статистикой разного рода ошибок.
HTTP2 - без этого тоже не получится обойтись, поскольку наличие HTTP2 позволит увеличить скорость загрузки ваших файлов и увеличить количество потоков для их загрузки.
Gzip - включение данной технологии позволит увеличить скорость загрузки и уменьшить объем пересылаемых данных.
Сервер объектного хранения - данный сервер позволит вам хранить картинки, документы и много чего еще не в репозитории, а на отдельном сервере, который может масштабироваться как вашей душе угодно. Я рекомендую использовать MinIO (https://min.io/). Он позволяет хранить временные файлы и автоматически их удалять по таймеру, отдавать в множество потоков статику и вообще он красавчег.
Я нарочно не стал указывать инструменты для SSR и прочие SEO штуки, так как они очень зависимы от стека, который вы используете. Некоторым они вообще не нужны. Вышеизложенное позволит вам выстроить лестницу наверх, при этом не сильно расплавив ваш любимый стул.
Вывод
Как уже не раз говорил: сначала думаем, потом делаем. Довольно редко я встречал приложения с чистым micro-frontend’ом. Чаще всего используется разного рода гибриды, которые зависят от стека и потребностей конкретного проекта. Не могу сказать, что micro-frontend это панацея и его точно не стоит затягивать во все проекты подряд. Не всегда монолит плох, как это стараются преподнести в свете последних тенденций. Он хорош тогда, когда у вас много команд и проект сложнее среднего. Помните, что вам работать с тем монстром, которого вы породите и постарайтесь сделать так, чтобы вам не было за него стыдно, когда вы уйдете с проекта.
P.S.
Я с радостью подискутирую на данную тему со всеми желающими, а также готов поделиться своим опытом и знаниями в приготовлении micro-frontend’а. Статья получилась объемной и всех нюансов просто не упомнишь. С радостью отвечу на любые вопросы.
Комментарии (19)
alexiusp
02.09.2021 18:58Я бы не стал на микрофронтендовые модули ставить прикоммитный хук с линтером и тестами. У меня в одной либе стоит билд и преттифай и каждый коммит уже занимает в районе минуты. Линтинг и тесты лучше проверять в CI на пулл-реквесте и не давать мержить пока всё не поправят.
XeaX
02.09.2021 19:17+3Чем был плох предыдущий подход - монолит?
Монолитное приложение характеризуется высокой связанностью частей системы.
На мой взгляд классическое "смешать тёплое с мягким". Можно сильно запутать между собой микрофронтенды, аналогично тому, как из микросервисов создают "распределенный монолит". А можно создать слабо связанное между модулями одно веб-приложение.
alexiusp
04.09.2021 02:33в монолите получить высокую связность гораздо проще и сложнее отслеживать эти самые связи. Но конечно, следить за связностью нужно и там и там, это скорее вопрос архитектуры, который в данной статье не рассмотрен и к теме монолит/микрофронтенд не совсем относится.
DeathSAAD
05.09.2021 11:06Так-то оно так. Только одно но:
в монолите за связностью должен следить кто-то (ревьюер, например), никакие паттерны не дают гарантий и безопасности
в микросервисах вы этого можете добиться ограничением на видимость сервисов и разделением на слои, т.е. архитектурно
Отсюда вывод: микросервисы позволяют эффективнее контролировать уровень связности.
pesh1983
05.09.2021 12:01+1Микросервисы - это не "серебряная пуля". У них есть свои проблема, которые отсутствуют в монолите, например, увеличивающаяся сложность поддержки, более жестокие требования к инфраструктуре, обеспечение консистентности данных при их распределенном хранении, необходимость контрактного тестирования, сложность отладки и профилирования.
Если люди, пишущие код, не способны контролировать его связность, у вас точно так же в микросервисе будет сильная связность. Но таких проектов уже будет не один. Тут надо повышать квалификацию инженеров или вводить тех лида/архитектора для контроля. Но микросервисы вам тут не помогут)
Evil_Evis Автор
05.09.2021 17:38Поддерживаю вас. Микросервисы и микрофронтенд всего лишь инструменты. как Говориться и микроскопом можно забивать гвозди только зачем? По этому я и призываю сначала думать а оно вам надо и только потом делать...
gis
06.09.2021 11:55"данный сервер позволит вам хранить картинки, документы и много чего еще не в репозитории, а на отдельном сервере, который может масштабироваться как вашей душе угодно"
Ради любопытства.
Чем в данном конкретном контексте minio принципиально лучше, чем любой web-сервер (apache httpd, nginx, lighttpd, MS IIS и т.д. и т.п.)?Evil_Evis Автор
06.09.2021 14:52если интересно подробно вот вам пару статей которые позволят понять нюансы https://habr.com/ru/company/veeam/blog/517392/ и вторая https://itsecforu.ru/2020/10/20/%F0%9F%A6%A9-%D0%B8%D0%B7%D1%83%D1%87%D0%B0%D0%B5%D0%BC-minio-%D0%B0%D0%B2%D1%82%D0%BE%D0%BD%D0%BE%D0%BC%D0%BD%D0%BE%D0%B5-%D1%85%D1%80%D0%B0%D0%BD%D0%B8%D0%BB%D0%B8%D1%89%D0%B5-%D0%BE%D0%B1%D1%8A/ Как минимум minio - имеет больше настроек и решает больше задач связанные с файлами статики.
gis
06.09.2021 15:04не, что такое минио - я в курсе. Меня интересует именно то, о чём я написал.
Если для задач UI надо хранить (и соответственно отдавать по HHTP) статичные файлы - то чем (и в каких конкретно ситуациях) выбор минио предпочтительнее веб-серверам?Лично для меня это предпочтение не очевидно.
XeaX
"Сборка релиза" убивает большую часть отличий микрофронтендов от модульного фронта.
Evil_Evis Автор
И да и нет, тут больше как зависимость пакетов. просто один из модулей типа умеет рисовать пони и собачек, а другой ожидает только пони... и вроду у нас должно быть 2 животных а по факту один . ну или оброатное что мы надеемся что модуль умеет больше чем на самом деле и тогда боль
Xuxicheta
Суть микрофонтендов не в том, кто чего умеет, а в том, что они релизятся независимо друг от друга и общаются только по очерченному контракту. И могут быть основаны на какой-угодно технологии, написаны разными командами.
Evil_Evis Автор
а я вроде ни где не упоменаю что технологии не могут быть разными, один модуль ангуляр, допустим формы. Сам каркас реакт, а какой-нибудь слайдер свелт или вью. просто подготовка отдельных независимых модулей накладывает определенные обязательства и к этому надо быть готовым
Xuxicheta
Ну про технологии это скорее бонус, а суть в независимых релизах, которую вы описали как сомнительное преимущество. Микрофронтендовая архитектура позволяет командам пилить свои участки не оглядываясь на ритм других команд. Об этом и первый тут коммент. В чем разница, если у нас релиз собирается, разницы нет.
Evil_Evis Автор
Может я не верно выразился, смысл в том что может быть изменения контракта что за собой повлечет за собой корректировку способа взаимодействия. Я рассматривал утрированный пример. Но вы правы. Я вроде не говорил что нельзя релизится по-отдельности
XeaX
На мой взгляд если вам не надо релизиться независимо, то скорее всего микрофронтенды будут ненужным оверхедом.
Evil_Evis Автор
Поддерживаю вас, все должно быть по задачам. Просто видел много проектов в которых пытаются сунуть то что там к черту не сперлось. Об этом и говорю...