Привет, Хабр! Я Расим Сулейманов, инженер-программист в ПСБ. Микросервисная архитектура приложений становится все более популярной, особенно среди крупных команд разработчиков. У этого подхода есть много преимуществ по сравнению с монолитом, но перейти на него получается не у каждого проекта. В этой статье разбираю на примерах, как превратить монолитную архитектуру в микросервисную, и рассказываю, зачем это делать.

Если смотреть глобально, разница между монолитной и микросервисной архитектурой приложений очевидна:
🔹Монолитная архитектура — одно большое приложение, которое запускается на веб-серверах как один процесс. Его кодовая база хранится в одном репозитории. Разработка, тестирование и деплой тоже происходят в едином потоке.
🔹Микросервисная архитектура — когда маленькие части из монолита делятся на целевые группы. Каждая из них хранится в отдельном репозитории, а разработка, деплой и тестирование каждого микросервиса может производиться независимо от других.
Давайте рассмотрим на простом примере разницу между этими архитектурами и разберемся, как можно разобрать монолит на микросервисы.
Вот как выглядит базовая монолитная архитектура написанного на ASP.NET Core банковского приложения:

В нашем приложении-примере есть три типа платежей: обычные, СБП и с цифровым рублем. Получается трехуровневая архитектура с контроллерами, моделями и слоем по работе с базой данных.
Вот как оно будет выглядеть после запуска в Swagger:

Чтобы перейти к микросервисной архитектуре, нам нужно разбить это приложение на составные части. Допустим, мы хотим сделать микросервис для платежей по СБП. Создаем новое ASP.NET Core приложение и переносим из монолита контроллер СБП в соответствующую папку в новом приложении. Я дополнительно разделил контроллеры на B2B и B2C платежи — в дальнейшем таких делений будет больше. Контроллер нужно зарегистрировать в DI и добавить порт, на котором оно будет работать. Ура, у нас есть первый микросервис.

Вот как он выглядит в Swagger:


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

Это эффективное решение, у которого есть много преимуществ:
слабая связь между фронтендом и микросервисами в бэкенде;
уменьшение количества вызовов между клиентами и микросервисами;
повышение безопасности за счет централизованной SSL-терминации и аутентификации;
централизованное управление общими задачами — ведением журнала, мониторингом, троттлингом и балансировкой нагрузки.
Однако есть и проблемы:
Сбой в API-шлюзе становится единой точкой отказа в микросервисной архитектуре. Все запросы идут через шлюз, и если он выйдет из строя, то все микросервисы тоже.
Увеличивается задержка из-за дополнительного сетевого вызова.
Если не масштабировать шлюз, он может стать узким местом для всей системы.
Это дорогое удовольствие как для разработки, так и для обслуживания.
Еще один возможный паттерн для распределения микросервисов — это backend for frontend (BFF). Этот метод выделяет микросервисы, которые нужны каждому фронтенд клиенту. Наше схематичное приложение можно разбить на сервисы для мобильного и веб-клиента: например, в вебе нужна SEO-оптимизация, а для мобильного клиента это будет бесполезный сервис. Функционал для каждого клиента распределяется по отдельным шлюзам. Вот как это выглядит:


На рынке представлен большой выбор API-шлюзов — есть и продукты от больших корпораций, и open source решения:
Amazon API Gateway
Azure API Management
WSO2 API Manager
Ocelot
King
Apigee
Microsoft YARP
В ПСБ мы работаем с open-source-решением Ocelot. На его примере покажу, как настроить и сконфигурировать API-шлюз.
Подключить Ocelot просто:

Конфигурация выглядит более запутанно, но на самом деле тут тоже нет ничего сложного.

Мы работаем с .json файлом, и в основном нас интересует секция Route — это наши маршруты. Настройка SwaggerKey поможет нам в дальнейшем тестировать сервис через Swagger. UpstreamPathTemplate — входящий шаблон, по которому запрос попадает в шлюз. DownstreamPathTemplate — исходящий шаблон, по которому запрос пойдет в микросервис. DownstreamHostAndPorts — стенд, где хостируется микросервис и куда пойдет запрос.
Чтобы протестировать Ocelot в Swagger, нужно добавить open-source-библиотеку MMLib.SwaggerForOcelot.

Расскажу о других полезных настройках в API-шлюзах на примере Ocelot, но многое из этого применимо и к другим решениям.
🔹Ограничение скорости (или троттлинг)
Если мы хотим ограничить скорость запросов или доступ к сервисам, можно добавить секцию для нашего маршрута RateLimitOptions. Нужно прописать True в графе EnableRateLimiting и сконфигурировать лимит на количество запросов в определенный период. Также здесь есть опция ClientWhitelist для клиентов, которых мы не хотим включать в список попадающих под ограничение скорости.

🔹Кэширование
Добавляем в startup настройку AddCacheManager и к маршруту добавляем опцию FileCacheOptions с указанием на сколько секунд мы хотим закэшировать запрос. Если запрос попадает на этот маршрут, то последующие запросы не будут нагружать микросервисы, а будут браться из кэша.

🔹Агрегация запросов
Один из главных плюсов шлюзов в том, что они могут уменьшить количество запросов. Это происходит в том числе благодаря функционалу агрегации запросов. Например, нужно собрать данные с трех микросервисов. Вместо того, чтобы отправлять три запроса с Angular приложения, мы можем отправить один через шлюз. Он распределит этот запрос внутренней сети, агрегирует их в единый JSON и отдаст на фронтенд.
🔹Другие полезные функции: аутентификация и авторизация (можно подключить различных провайдеров), логирование, телеметрия, балансирование нагрузки.

Переходить на микросервисы просто потому, что это мейнстрим, не стоит. У обеих архитектур есть свои плюсы и минусы.
🔹Плюсы и минусы монолитной архитектуры:
✅Легкость разработки. В монолитной системе все компоненты связаны, поэтому их проще писать и тестировать. В единой базе кода также проще понять логику приложения.
✅Производительность. Время выполнения в монолитной архитектуре ниже, поскольку все компоненты выполняются в рамках одного процесса. Сокращаются затраты на межпроцессное взаимодействие, нет дополнительных расходов в виде шлюзов и других элементов микросервисной архитектуры.
✅Меньшие расходы. Запуск и обслуживание дешевле, чем в микросервисной архитектуре. Например, нет дополнительных расходов на внедрение и поддержку шлюзов.
❌Сложнее масштабировать. Монолитная архитектура масштабируется вертикально, и это может вызывать проблемы при резком увеличении нагрузки и необходимости масштабироваться горизонтально (например, сезонных продажах или событийных всплесках трафика на сайт).
❌Сложнее обновлять и поддерживать. Любые изменения в монолитной архитектуре нужно вносить во все приложение сразу. Это может приводить к заторам в разработке — если одна команда не готова к релизу, все должны ждать, пока она закончит. Любую отладку и тестирование тоже нужно проводить по всей кодовой базе — это более затратно, и не всегда дает точные результаты.
❌Труднее внедрять новые технологии. Монолитное приложение сильнее привязано к тем технологиям, на которых оно построено. Даже если вы хотите поменять только одну часть, придется разбирать всю систему.
🔹Плюсы и минусы микросервисной архитектуры:
✅Масштабируемость (особенно горизонтальная). При повышенной нагрузке на один из микросервисов, вам нужно масштабировать только его, а не всю систему.
✅Гибкость разработки. Элементы приложения можно разрабатывать, тестировать и деплоить независимо друг от друга. Команды могут использовать разные технологии и подходы для каждого сервиса и не конфликтовать друг с другом.
✅Повышенная отказоустойчивость. В микросервисной архитектуре как максимум из строя может выйти шлюз, отвечающий за несколько сервисов. Его можно починить и протестировать отдельно от других функций приложения. В монолите если ломается что-то одно, то ломается все приложение.
❌Сложность управления. Вам потребуется много инструментов для мониторинга, развертывания и управления сервисами и шлюзами. Это довольно большой скачок в сложности по сравнению с монолитом, не каждая команда к этому готова.
❌Увеличение затрат. Изначальное развертывание и дальнейшая поддержка микросервисной архитектуры — это дополнительные расходы. Конечно, для тех компаний, для которых актуальны плюсы этой архитектуры, затраты окупаются, но это можно сказать далеко не про все организации.
❌Сложность обеспечения целостности данных. В микросервисной архитектуре могут возникать проблемы с согласованностью и обменом данных между сервисами. Контроль этих процессов — опять же, дополнительные затраты.
В целом я бы рекомендовал микросервисную архитектуру для зрелых, крупных компаний. Небольшой пул разработчиков может адаптироваться к конфликтам и заторам, которые возникают при работе в монолите. Когда речь идет о десятках команд с разными релизными политиками, это куда более серьезная проблема.
Также к микросервисам стоит присмотреться тем организациям, у которых бывают частые перепады в нагрузке. Однако вам нужно решить, выгоднее ли вам вкладываться в новую архитектуру или просто покупать дополнительные мощности под всплески активности.
Если вы не уверены, можете начать с монолита, подготовить его к модульной системе, а уже потом потихоньку распиливать его на микросервисы. Главное — ориентироваться на потребности и возможности вашей организации. Переход на микросервисную архитектуру — сложный проект, который стоит свеч для тех компаний, которые могут извлечь из него максимум пользы.