Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Всем привет! На связи Максим Смирнов, архитектор по фронтенду в Тинькофф. В серии статей будет история о том, как мы переписывали один из монолитных сервисов на Federation.
Расскажу о том монолите, который переписали, и как дошли до момента, что надо распилиться. Еще покажу, какие фишки мы накрутили в Module Federation, потому что из коробки многих фич нет, надо докручивать самим. Добро пожаловать под кат!
О понятиях и монолите
Прежде чем перейти к распилу, давайте договоримся о том, какие слова будем использовать.
сборная солянка с примесью спагетти</p>" data-abbr="Монолит">Монолит — все в одном: бизнес-код, функциональщина, все перемешано.
Микросервис — сервис, выполняющий определенную бизнес-задачу. Это достаточно современный подход в бекенде и во фронтах.
Module Federation — плагин от Webpack, позволяющий без проблем и костылей стать микрофронтендерами.
Монолит — это вам не микросервисы с правильной архитектурой, компоновкой компонентов и разделенной бизнес-логикой. Как бы смешно ни звучало, но почти все монолиты похожи друг на друга.
Роутинг нашего приложения был похож на личные кабинеты у многих компаний:
Мы не рассматривали микрофронтенды, когда думали о распиле. Хотели просто освежить проект — провести небольшой рефакторинг, поменять устаревшие зависимости и прибраться в коде, чтобы все было красиво и на своих местах. Сначала перевели на nx-workspace монорепу, которая у нас в компании стандарт. Даже в одном приложении мы используем nx-workspace, потому что большое количество тулинга для него и из коробки nx-workspace имеет много генераторов, линтеров и команд для запуска тестов и прочее полезное. Потом вынесли в лейзи все, что не было вынесено раньше у монолита. У нас был код, который писали с 2014 года, и его витиеватость зашкаливала. Рефакторинг проводили очень аккуратно, чтобы не сломать в неочевидном месте. Все места, где перемешана бизнес-логика с функциональностью, переписали и вынесли в лейзи-модули.
Мы декомпозировали весь код по библиотекам. Думаю, многие сталкивались со спагетти-кодом, когда в двух разных местах один и тот же код протянут через все приложение идеальным подходом — Ctrl+C, Ctrl+V. Nx позволяет использовать локальные библиотеки, которые можно шарить внутри между приложениями или сервисами. Еще можно перенести в npm-библиотеку, если мы планируем переиспользовать функциональность в других приложениях вне этого репозитория.
Все, что можно распилить и имеет больше одной реализации, мы выносили в библиотеку и переводили приложения на использование библиотек.
Самый веселый элемент — это шаринг данных между страницами. Я думаю, многие сталкивались с такой вещью, как state management — ngrx, ngxs, redux. У монолита все описания состояний были в rооte. Мы определили:
границы данных;
начало и конец бизнес-состояния конкретной страницы;
что оставляем в roote.
По такому чек-листу мы пошли перепиливать и оптимизировать.
И самое важное, что вытекает из шины данных: мы решили перевести на фича store большие страницы. Потому что можно сделать свое состояние, использовать только его и ни на кого не завязываться. Не нужно ничего хранить в рутовом состоянии.
Мы получили чуть более красивый монолит, сделали его модульным. В случае чего мы могли его расширять, а бизнес-логика была не связана между собой. Мы выработали перечень единых подходов, как передавать данные между модулями и как им общаться.
Любой фронт — это монолит. Весь вопрос в том — а что внутри? Внутри спагетти и монолитность или за монолитностью этого бандла скрывается модульность, лейзики, подгрузки. С такими монолитами можно жить.
Сбой — как мотиватор распила
Думаю, многие сталкивались с главной проблемой монолитов: если упал один — упадет всё.
Всего лишь один pull request с дифом на 1000 элементов и одна забытая точка с запятой в нем может убить прод.
Так случилось у нас. Однажды из-за невнимательности на ревью мы пропустили критическую ошибку в коде. Тесты пишут немногие: это же монолит, легаси, вот будут микросервисы — будут и тесты. Проверку на стенде тоже не проводили: были уверены в своем коде, ну как это бывает — задача-то горит! А так как у нас монолит, сделав ошибку в одном модуле, мы получили полностью лежащее приложение. Такое никому не нравится. Поэтому мотиватором распила и перехода в микрофронты стали сбои — их было много.
Сбой в монолите — это:
Недоступность всего функционала. При сбое в монолите обычно недоступно все. С бэкендом полегче: если он упал, то он упал. Понятно, что мы зависим от бэка, но мы можем это обработать и показать клиенту красивую картинку, например с гаечным ключем — красиво же! Фронт же рушится ступенчато, но с полной недоступностью по большей части. Ошибка в одном файле может прервать загрузку всего приложения. И настанет он — «Господин белый экран».
Сложная идентификация триггера сбоя. В монолите сложно найти триггер, который спровоцировал падение. Триггер сбоя — это не деплой в пятницу вечером, а тот маленький кусочек кода в этом дифе на 1000 элементов, который породил проблему.
Постоянное давление со стороны бизнеса. Когда упало приложение, нам нужно его срочно поднять. Если down time 5—10 минут, обычно приходит в созвон продакт и начинает бить палкой: давайте скорее, у нас все лежит, надо делать быстрее. Мы стараемся, но как найти эту точку с запятой в дифе? Конечно, можно откатить, но это очень просто, а простыми путями мы не ходим.
Оптимизация после сбоев как триггер для нового сбоя. Монолит страшен тем, что есть куски кода, о существовании и логике работы которых можно просто не знать. Потому что не мы их писали и костылики, которые провоцируют эти сбои, написаны не просто так. А значит, после фикса нашего сбоя мы будем исправлять проблему, чтобы она не появилась дальше, и, скорее всего, получим еще один сбой, когда будем выкатывать это решение.
Сбой в микросервисе — это:
Недоступность только части функционала. Когда сбой в микросервисе — понятно, за какую часть он отвечает, можно посмотреть, кто и что сделал с этим приложением: изменял приложение, бизнес-код или другое. В случае недоступности микросервиса мы получаем только одну неработающую часть функционала, все остальное приложение продолжает работать.
Легкий поиск триггера. Найти триггер просто: вы знаете сервис, который сбойнул, и можете посмотреть его релизный диф. Я думаю, что в микросервисе не будет дифа на 1000 элементов, максимум 150.
Давление со стороны бизнеса будет всегда. К сожалению, давление от бизнеса будет постоянно, и не важно, что вы используете монолит или микросервис. Бизнесу безразлично. У них недоступный функционал, продажи лежат — все очень плохо.
Полечить место сбоя легче, все замкнуто на микросервис. Потому что код свежий и, скорее всего, он задокументирован и описан где-то на Вики. Поэтому вы знаете, как эта штука работает. И если там есть костыль, там явно есть js doc, который говорит, почему этот костыль там появился и что он фиксит.
Вместо заключения
В первой части мы поговорили о том, что такое монолит, как мы навели порядок в своем монолите, чтобы комфортно его разрабатывать и поддерживать. И о причинах, подтолкнувших нас к пересмотру архитектуры приложения.
Во второй части расскажу, как наведение порядка и сбои подтолкнули нас к использованию микрофронтового подхода, с какими вызовами мы столкнулись и как героически с ними боролись. А еще — о том, что мы захотели накрутить поверх наших микрофронтов для удобства пользователей и разработчиков сервисов.