Привет Хабр! Меня зовут Евгений Лабутин, я фронтенд-разработчик в МТС Digital. Расскажу вам о том, как мы приручили микросервисы на нашем проекте МТС Твой бизнес, зачем они нам вообще понадобились и какую выгоду мы от этого получили. Также я поделюсь кодом одного из наших микросервисов в Open Source.
В этой статье я не буду рассуждать о плюсах и минусах микросервисов в целом, подобных публикаций на Хабре и так хватает. Я расскажу про личный опыт внедрения микросервисов и тех преимуществах, которые получили именно мы. А для того, чтобы вам проще было понять причинно-следственную связь, рассказ я буду вести в хронологическом порядке.
Отправная точка
Началось все с монолитного репозитория, включающего в себя 5 приложений: лендинги, кабинет, 3 промо-приложения и каталог общего кода, который шарился между всеми приложениями.
На проекте мы использовали Чистую Архитектуру, поэтому никаких проблем с точки зрения написания большого количества логики и масштабирования приложений не испытывали. С git работали по процессу чистого gitflow.
Но с ростом приложения начались проблемы организационного и технического характера. Наше релизное тестирование стало занимать слишком много времени, так как в релиз попадало чрезмерно много разнородного кода. Иногда релизы подвисали из-за внешних факторов, которые лишали нас возможности выпустить следующий релиз, не связанный с функционалом актуального. Технические проблемы – появление задач, которые нельзя реализовать на стеке монолита.
Использовав микросервисный подход, мы получили больше свободы в выборе стека.
Пререндер BotView
Проблема: Наше веб-приложение выполнено в виде статичного html и js. Когда приложение создавалось, NextJS и SSR еще не были такими популярными, в моде были статичные сайты. Но большинство поисковых ботов полноценно не видят контент, который рендерится на стороне браузера (обленились, видимо, все им подавай готовое). А из-за того, что боты не видели сайт, у нас было мало SEO-трафика.
Решение: Мы приняли быстрое и простое решение – сделать микросервис, который возвращает поисковым ботам уже отрендеренную страницу. Для этого мы взяли библиотеку Puppeteer JS, соединили его с фреймворком NestJS и «закатали» в контейнер. Puppeteer JS использует не целый браузер, а только браузерный движок от Google Chrome, что очень хорошо сказывается на его производительности и потребляемых ресурсах. Микросервис получился очень быстрым и надежным, особенно если сравнивать его с контейнером от prerender.io.
Преимущества: После запуска микросервиса боты стали хорошо индексировать наш контекст и проблемы с SEO ушли. Кроме того, наш пререндер не завязан ни на какой фреймворк, он может рендерить jQuery, AngularJS, CRA и любой другой. Также он выгодно отличается от SSR тем, что поддерживает WebComponents, Микрофронтенды, Динамический контент, а разработчикам не надо тратить силы на написание кода, способного исполниться на стороне сервера.
Github: Выложено в opensource.
Будущее: В дальнейшем мы планируем внедрить Next.js и SSR.
Сниппет для социальных сетей
Проблема: Появилась задача – генерировать сниппеты с картинками для социальных сетей. Проблема была в том, что вариативность этих сниппетов слишком большая и генерировать их заранее не представлялось возможным, так как они занимали бы слишком много места. Передать эту задачу бэкендерам не удалось, они были сильно загружены.
Решение: Мы решили сделать микросервис, который генерирует сниппеты на лету в зависимости от входных параметров. Для этого мы взяли библиотеку Node Canvas, соединили его с фреймворком NestJS и собрали в контейнер. С Canvas умеет работать почти любой фронтенд-разработчик, кроме того, у наших фронтендеров хорошие понимание композиции и чувство стиля.
Преимущества: Теперь картинки для социальных сетей – это отдельный микросервис, фронтендеры сами его поддерживают без помощи бэкендеров и решают задачи фронтенда самостоятельно. Картинки генерируются на лету очень быстро и кешируются на проксе, поэтому сам микросервис не нагружает серверы.
Оптимизатор картинок
Проблема: На наших лендингах очень много картинок. Мы используем картинки, оптимизированные для конкретного браузера и экрана пользователя. Браузерам, поддерживающим Avif, выдаем в этом формате, старым браузерам – выдаем jpeg. Retina-дисплеям выдаем огромные картинки, слабым телефонам с маленьким экраном – маленькие картинки. Проблема в том, что оптимизировали мы на этапе сборки проекта, а с ростом количества лендингов эти оптимизации стали занимать слишком много времени сборки. Кроме того, такая оптимизация не позволяет работать с динамическими картинками, добавляемыми из админки.
Решение: Мы решили сделать микросервис который генерирует оптимизированные картинки на лету в зависимости от входных параметров. Для этого мы взяли библиотеку SharpJS, соединили ее с фреймворком NestJS и упаковали в контейнер. Библиотека SharpJS конвертирует даже крупные картинки в считанные миллисекунды, а прокси-сервер их кеширует. Про этот микросервис я уже подробно рассказывал на Хабре.
Преимущества: Мы здорово выиграли на времени сборки. Теперь все картинки генерируются на лету и кешируются на прокси-сервере. Поэтому наш сервер не испытывает нагрузки, а клиент получает молниеносный ответ из кеша. Кроме того, появилась возможность работать с динамическими картинками, загружаемыми из админки или других динамических мест.
Github: Выложено в opensource.
Статика
Проблема: Картинок в принципе стало слишком много. Элементарное их копирование в контейнер на этапе сборки превратилось в проблему. А зачем это вообще делать каждый раз при сборке, если изображения меняются редко? Кроме того, встала проблема, что одни и те же картинки надо шарить между разными микросервисами. И где их тогда хранить?
Решение: Мы вынесли всю статику из всех микросервисов в отдельный репозиторий статики. Туда же забрали всю статику от бэкендеров. При сборке мы просто копируем ее в контейнер и хостим через nginx.
Преимущество: Теперь в наших репозиториях действительно лежит только код и никаких бинарников. Время сборки приложений сильно сократилось, а картинки теперь публикуются только тогда, когда они действительно поменялись.
Будущее: Планируем полностью переложить статику в уже используемое в МТС объектное хранилище и подключить CDN. И вместо микросервиса статики сделать микросервис управления статикой.
Промо-приложения
Проблема: Кодовая база нашего монолита все равно продолжала расти и мы столкнулись с проблемой, что железу разработчиков стало слишком тяжело собирать такое количество кода. Время сборки в режиме watch стало занимать 9 секунд на Macbook Pro, что обернулось некоторым дискомфортом при разработке. При этом в монолите было 3 промо-приложения, которые никак не дорабатывались, просто лежали балластом и потребляли ресурсы.
Решение: Мы вынесли промо-приложения в отдельный микросервис. Скопировали код как есть, удалили ненужное и опубликовали в отдельном контейнере.
Преимущество: Теперь остатки нашего монолита разрабатывать намного комфортнее, сборка почти мгновенная. А при обновлении библиотек можно не заморачиваться поддержкой промо-проектов, ведь у них теперь свой стек в отдельном репозитории.
Новое промо для МТС Маркетолог
Проблема: Тут мы сразу поняли, что не хотим никаких проблем =)
Решение: Изначально разрабатывали как отдельный микросервис на более современном стеке. Для этого мы взяли современный фреймворк NextJS, разработали приложение и отправили в контейнер.
Преимущество: Все прошло идеально, разработчику микросервиса не пришлось синхронизировать с кодом монолита. Когда в монолите завис релиз на подписании документов у юристов, микросервис был выведен в продакшен без влияния на релизный цикл монолита. Кроме того, мы начали использовать новый фреймворк. А за счет того что он в отдельном репозитории, можно не заморачиватьcя внедрением нового фреймворка в легаси промо-проекты.
Блог
Проблема: Блог нам достался от другого проекта. Задача стояла такая – перенести часть чужого проекта в наш. Кроме того, стек на проекте немного отличался от используемого у нас. Представляете, сколько это боли – перенести и совместить куски монолитов?
Решение: Мы взяли чужой проект, отпилили от него все лишнее и опубликовали у себя как микросервис.
Преимущество: На весь перенос ушло два часа. Фактически это были перенастройка CI/CD и удаление лишнего кода, никакой боли и страданий.
Логи
Проблема: У нас был замечательный логгер, но в связи с миграцией продуктового окружения в новый контур, мы больше не могли собирать логи в старой системе агрегации логов. Поэтому нужен был сервис для того, чтобы подружить наш логгер с корпоративной Kibana. Переписывать систему логгирования мы не хотели, она нас полностью устраивала.
Решение: Мы сделали микросервис, который получает логи от веб-приложения и выводит их в stdout контейнера. Далее стандартными инструментами контейнеров эти логи считываются и отправляются в ELK, ровно так, как это делает обычное бэкенд-приложение. Весь микросервис состоит из фреймворка NestJS с одной точкой API, который принимает запрос, извлекает тело и пишет в stdout.
Преимущества: Теперь у фронтендеров есть вся мощь ELK для сбора и анализа логов.
Github: Выложено в opensource.
Emails (в планах)
Проблема: Тестировщики не могут протестировать верстку письма без привлечения разработчиков. У них просто нет возможности отправить себе письмо на почтовые ящики. А у фронтендеров нет возможности обновить верстку письма, не привлекая внимания бэкендеров, ведь письмо переносится на бэкенд вручную.
Решение: Сделать микросервис писем. Микросервис, где тестировщик может зайти в интерфейс, выбрать интересующее его письмо, вбить тестовые данные в тело письма и отправить письмо на свои тестовые почтовые адреса. Для бэкендеров – сделать API, по которому они будут забирать уже готовую верстку письма. Тем самым фронтендер сможет обновлять письмо без участия бэкендера.
Преимущество: Упрощение процесса разработки электронных писем.
WebPrometheus (в планах)
Проблема: На серверной стороне мы снимаем большое количество метрик и выводим их в красивом интерфейсе Grafana. А вот для клиентских метрик мы таких инструментов не имели. Можно было, конечно, использовать инструменты аналитиков, но тогда мы потеряли бы удобство, которое дает Grafana. А хотелось бы видеть (как минимум) метрики WebVitals в реалтайме.
Решение: Написать микросервис, который будет принимать от клиента метрики, агрегировать и выдавать Prometheus для последующего отображения в Grafana, наподобие микросервиса логов.
Преимущества: Появится возможность более наглядно понимать, как те или иные решения сказываются на пользовательском опыте и перформансе клиентских приложений на реальных устройствах.
Компоненты
Проблема: Многие микросервисы используют большое количество одних и тех же компонентов. Но как же их переиспользовать, а не копировать между проектами?
Решение: Сделали микросервис разработки компонентов. Это workspace, состоящий из двух проектов, самих компонентов и документации к ним. Компоненты – это только код компонентов. Документация написана на NextJS, одновременно она выступает документацией и тестовой площадкой для разработчиков. Документация выполнена в классическом для компонентов стиле по аналогии с mui, ant design и тому подобными.
Преимущества: Теперь есть возможность разработать компонент в «вакууме». Компоненты хорошо описаны и по ним легко ориентироваться, новому человеку в команде будет гораздо проще погружаться. У разработчиков пропала возможность запихнуть бизнес-логику в компонент. Компоненты, разработанные таким способом, получаются гораздо более целостными, проработанными и завершенными.
Микрофронтенды
Проблема: В микросервисах есть части сайтов, переиспользуемые в микросервисах, а также у наших партнеров. Распространять эти части через компоненты не вариант. так как должна быть возможность оперативного обновления во всех местах использования.
Решение: Сделать микросервис разработки микрофронтендов. Микросервис реализован по аналогии с микросервисом компонентов, с тем лишь отличием, что результат распространяется не через реестры npm, а как микрофронтенд по https.
Преимущества: Плюсов много, о них можно отдельную статью писать. А вам интересно как у нас устроены микрофронтенды? Дайте знать в комментариях!
Результат
Вот так, микросервис за микросервисом, у нас получилась простая и гибкая архитектура сервиса, которую можно серьезно масштабировать. Кусочек монолита еще жив, но уже очень скоро и от него ничего не останется. Теперь у нас появилась возможность разрабатывать и релизить отдельные части проектов независимо друг от друга.
Кроме того, тестирование новых релизов занимает мало времени, ведь изменения теперь изолированы микросервисом. А малый срок тестирования позволил перейти от процессов gitlow к более простым и быстрым процессам githubflow. Теперь фичи не агрегируются в релизы, каждая фича фактически является микрорелизом. После разработки и тестирования она сливается напрямую в продакшн.
Как работаем теперь
Если разработчику необходимо реализовать новое письмо с картинками – он кладет картинки в репозиторий с изображениями и публикует в продакшн, не боясь убить лендинги. После отрабатывания CI/CD процессов разработчик верстает письмо с ссылками на картинки в продакшн. По окончанию разработки письмо передается тестировщику, а после тестирования – в продакшн, без риска «убить» блог.
Если разработчику необходимо доработать блог – он свободно его дорабатывает, внедряет новые библиотеки и фреймворки, не оборачиваясь на стек лендингов и кабинета. После окончания разработки и тестирования блок сливается в продакшн без страха положить оплаты.
А за счет того, что тестирование маленькое и изолированное, появляется возможность делать по 2-3 релиза в день по каждому микросервису. Если бы у нас было 100 микросервисов – это 200-300 релизов ежедневно без страха что-либо сломать.
Как выделить микросервис
Очень часто слышу вопрос «Как в монолите выделить микросервис?». Я выделил несколько принципов, по которым из монолита можно выделять микросервисы. Вот они:
разделы приложений. Лендинг, Кабинет, Админка, Блог, Справка, Документация – все это может быть отдельным микросервисом с изолированным стеком и технологиями;
изолированные приложения. Промо-проекты и промо-страницы;
статика. И пусть в репозитории останется только код;
инфраструктурные сервисы. Авторизация, Логи, Мониторинг – все они работают независимо друг от друга и помогают жить приложению;
вспомогательные сервисы. Пререндер, Сниппет, Компоненты, Микрофронтенды – все что не вписывает в монолит, но реализовать хочется.
Микросервисы действительно независимы?
Очень часто можно услышать каверзный вопрос. А что будет, если в микросервисной архитектуре ляжет один из важных микросервисов? Например, сервис авторизации. Тогда перестает функционировать весь сервис?
Нет, перестанет функционировать только авторизованная зона, остальные сервисы продолжат работать. Продолжат собираться SEO переходы, продолжит работать блог, продолжат работать информационные лендинги, продолжат проходить оплаты, продолжат работать рассылки. Перестанет работать только часть, завязанная на авторизации.
Но за счет изолированности микросервиса баг будет гораздо проще найти и починить. Главное – делать микросервисы действительно независимыми, а не распределенными монолитами.
Спасибо за уделенное статье время! Надеюсь, мой опыт вам пригодится. Если у вас есть вопросы или замечания – с удовольствием пообщаюсь в комментариях к статье!