Всем привет! На связи Максим Смирнов, архитектор по фронтенду в Тинькофф. В серии статей будет история о том, как мы переписывали один из монолитных сервисов на Federation.

Расскажу о том монолите, который переписали, и как дошли до момента, что надо распилиться. Еще покажу, какие фишки мы накрутили в Module Federation, потому что из коробки многих фич нет, надо докручивать самим. Добро пожаловать под кат!

О понятиях и монолите

Прежде чем перейти к распилу, давайте договоримся о том, какие слова будем использовать.

Монолит — все в одном: бизнес-код, функциональщина, все перемешано. 

Микросервис — сервис, выполняющий определенную бизнес-задачу. Это достаточно современный подход в бекенде и во фронтах. 

Module Federation — плагин от Webpack, позволяющий без проблем и костылей стать микрофронтендерами.

Монолит — это вам не микросервисы с правильной архитектурой, компоновкой компонентов и разделенной бизнес-логикой. Как бы смешно ни звучало, но почти все монолиты похожи друг на друга.

Любой монолит выглядит примерно так: есть роутинг, компоненты, слои с бизнес-логикой, айпишки, бэкенды. Так выглядел и тот монолит, который мы решили пилить
Любой монолит выглядит примерно так: есть роутинг, компоненты, слои с бизнес-логикой, айпишки, бэкенды. Так выглядел и тот монолит, который мы решили пилить

Роутинг нашего приложения был похож на личные кабинеты у многих компаний:

У роутинга был рутовый slash, определенные страницы, по которым ходил роутинг. Эти страницы имели свою бизнес-логику: где-то показывать графики, где-то — операции, где-то — магазины. Каждая дальнейшая страница имела внутренний роутинг
У роутинга был рутовый slash, определенные страницы, по которым ходил роутинг. Эти страницы имели свою бизнес-логику: где-то показывать графики, где-то — операции, где-то — магазины. Каждая дальнейшая страница имела внутренний роутинг

Мы не рассматривали микрофронтенды, когда думали о распиле. Хотели просто освежить проект — провести небольшой рефакторинг, поменять устаревшие зависимости и прибраться в коде, чтобы все было красиво и на своих местах. Сначала перевели на 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, который говорит, почему этот костыль там появился и что он фиксит.

Вместо заключения

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

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

Комментарии (2)


  1. devenji
    00.00.0000 00:00

    Было интересно, спасибо за статью!


  1. yuriy-bezrukov
    00.00.0000 00:00

    Разработчик написал фичу, прошло ревью, ветку помержили и отправили в прод...

    Т.е. при выкатывании новых фич отсутствует процесс тестирования?

    В чем именно сложность найти проблему если можно пройтись по колл стейку и определить проблему, обычно это происходит быстро

    Если частая проблема с тем, что падает прям всё приложение в рантпйме, то можно сделать пару дымовых е2е тестов

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