Привет! Эта статья написана командой системных аналитиков из ПСБ:
 Юрием Моргуновым, Анастасией Самсонниковой, Ольгой Догадкиной, Максимом Белявским и Максимом Зуевым. Мы, как и многие компании, перешли с монолита на микросервисы. Причины тривиальны — их озвучивают, пожалуй, в каждой истории ухода от монолита: поддерживать всё это стало дорого, неудобно, монолитная архитектура замедляла процессы. 
 
 В общем, здесь начинается наша история. Если вы планируете переходить на микросервисы, эта статья для вас. Если же вы уже раздробили свой монолит, заглядывайте и дополняйте нас в комментариях.

В то же время в ПСБ проходило импортозамещение ряда программных продуктов. В частности, миграция с MS SQL на PostgreSQL, поэтому задача стала ещё интереснее.

Итак, микросервисы. Коротко пробежимся по их плюсам и минусам и пойдём к нашей задаче.

Плюсы:

  • Гибкое масштабирование. При монолитной архитектуре рост нагрузки требует вертикального масштабирования (усиление сервера), что быстро упирается в физические ограничения железа. А ещё нельзя точечно масштабировать проблемные компоненты. В условиях микросервисной архитектуры можно быстро развернуть новые экземпляры сервиса и снизить нагрузку.

  • Выше отказоустойчивость. Ошибка в одном сервисе не равна падению всей системы.

  • Гибкость технологий. Каждая команда выбирает стек под свои задачи с учётом своих условий и предпочтений.

  • Независимое развёртывание. Например, для добавления новой фичи достаточно отработать и пересобрать какой-то один микросервис, а не всё приложение целиком.

Недостатки у микросервисной архитектуры тоже есть:

  • Сложно правильно разделить: микросервис должен быть оптимального размера. Не слишком большим, не слишком маленьким. Дальше мы расскажем об этом поподробнее.

  • Трудозатраты на интеграции. Каждый новый сервис требует API, очереди сообщений, мониторинга.

  • Зависимость от других команд.  Чужие данные = долгие согласования (а в монолите просто взяли бы из БД).

  • Проблемы владения: если сервис ничей, он быстро устаревает.

  • Нужны зрелые DevOps-процессы и чёткое проектирование.

Вот что мы хотели получить от микросервисов в первую очередь:

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

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

Микросервисам быть! Что дальше?

Плохо спроектированные микросервисы создают те же проблемы, что и монолит, но с дополнительными накладными расходами. Здесь слово Ольге Догадкиной.

Успешность микросервисной архитектуры критически зависит от принципа «единой ответственности» (Single Responsibility Principle, SRP). Это означает, что у нас должно быть чёткое и однозначное понимание, какую единственную функцию или набор тесно связанных функций выполняет данный сервис. Микросервис должен иметь свою зону ответственности и отвечать за один точно определённый фрагмент бизнес-логики. Это не просто техническая группировка кода, а выделение конкретного бизнес-домена. Например, вместо монолита, управляющего всем, мы создаём отдельные сервисы: сервис управления заказами, сервис управления платежами, сервис уведомлений. Правильное определение зоны ответственности микросервиса и его границ делает его автономным, что впоследствии упрощает разработку, тестирование, поддержку и повышает гибкость всей системы.

Правильно спроектированный микросервис обладает максимальной внутренней связностью: внутри микросервиса находится необходимая бизнес-логика и наблюдается плотная связь между сущностями, изменения внутри не требуют правок в других сервисах. Такой микросервис не уронит вам всю функциональность, если в нём что-то засбоило, и не потребует вовлечения других команд.

Плохо спроектированный микросервис — это когда всё наоборот. Внутри себя микросервис содержит слабые логические связи, но плотно связан с другими микросервисами разными связями. Такая архитектура создаёт иллюзию декомпозиции, но по факту получается, что логика не находится внутри каждого отдельного микросервиса, а распределена между ними. При необходимости доработки такого микросервиса с большой долей вероятности потребуются доработки и в других микросервисах, с которыми он связан. А поскольку они могут находиться в зоне ответственности других команд, то, внося изменения в свой микросервис, мы автоматически добавляем в бэклог другой команды задачу, которую нужно согласовать, включить в спринт, ожидать реализации и далее синхронизировать релизы. Это замедлит выпуск в прод и, конечно, повысит time-to-market. Возникает вопрос: зачем было уходить от монолита? 

Важно не только определить границы ответственности, но и контролировать размер микросервиса.

Если увлечься декомпозицией, можно создать так называемые наносервисы — сервисы, которые выполняют одну примитивную операцию и не могут выполнить значимую часть бизнес-процесса.

Наносервисы приводят к серьёзным архитектурным проблемам:

  • Экспоненциальный рост сетевого взаимодействия: вместо того чтобы выполнить операцию внутри одного процесса (как в монолите), для решения одной бизнес-задачи приходится делать десятки сетевых вызовов между «наносервисами». Это резко увеличивает задержки и нагрузку на сеть.

  • Усложнение CI/CD-конвейера: сборка, тестирование, развёртывание 100 крошечных репозиториев становится куда сложнее, чем управление 10 сервисами оптимального размера.

  • Сложность тестирования и мониторинга: отслеживание одного пользовательского запроса, проходящего через 15–20 «наносервисов», существенно замедляется.

  • Получение «распределённого монолита»: из-за огромного количества взаимозависимостей и необходимости синхронного развёртывания всех компонентов система теряет гибкость и автономию, ради которых всё затевалось.

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

Как понять, какой размер микросервиса оптимальный? Оптимальный размер сервиса — это баланс между независимостью команд и минимизацией сетевых вызовов, продиктованный границами конкретного бизнес-домена, а не техническими метриками. Сервис должен быть достаточно большим, чтобы решать конечную задачу автономно, и достаточно маленьким, чтобы оставаться гибким и управляемым одной командой примерной численностью 10–12 человек.

Определить границы микросервиса нам помогли следующие шаги, во многом основанные на принципах предметно-ориентированного проектирования (Domain-Driven Design, DDD):

  • Чётко определили цели и назначение микросервиса.

  • Проработали, как именно пользователи и другие системы будут взаимодействовать с сервисом, что позволило выявить необходимые функции и операции.

  • Спроектировали концептуальную модель данных для визуализации ключевых сущностей внутри контекста, чтобы убедиться в высокой внутренней связности данных.

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

  • Рассмотрели показатели прогнозируемой нагрузки на микросервис.

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

  • Обратились к стратегии развития архитектуры. Тут важно помнить, что системные аналитики и архитекторы мыслят по-разному: архитектор смотрит на картину в общем и проектирует структуру в целом по системе, а аналитик намного больше погружается в детали бизнес-процессов и требований, проектирует таблицы, методы и схемы. Нужно балансировать разные уровни мышления.

Пилить монолиты не так-то просто… Первые грабли

Переход от монолитной архитектуры к микросервисной на бумаге выглядит просто, но на практике таит в себе множество подводных камней. Когда мы только начали процесс «дробления» нашего монолита, мы практически сразу столкнулись с рядом типичных, но критичных проблем. Расскажем, как мы всё это решили.

Первое — когда потребность создания микросервиса возникла сразу в нескольких командах, мы столкнулись со множеством вопросов. Как инициализировать новый сервис? Каков процесс выделения и настройки отдельной базы данных на тестовых стендах? Куда идти за техническими учётными записями и правами доступа? Как стандартизировать создание репозитория и оформить задачу в трекере? Чтобы оптимизировать процесс, минимизировать дублирование усилий и обеспечить единый подход, мы разработали два ключевых артефакта:

  • Пошаговая инструкция по запуску микросервисов. В этом документе мы детально прописали роли, ответственных лиц и конкретные действия для каждого этапа инициализации сервиса.

  • Шаблон документации микросервиса, содержащий всю критически важную информацию для описания микросервиса: название и предназначение сервиса, реквизиты БД и ссылки на репозитарии, ссылки на API и схемы зависимостей и другие полезные вещи.

Эти инструменты позволили нам формализовать процесс и масштабировать подход к разработке микросервисов.

Второе — столкнулись с проблемами «частичного отказа», когда фронтенд отправляет запросы к нескольким микросервисам для загрузки одной страницы, но ответ от бэкенда приходит с ошибкой.  Тут мы продумали логику обработки ошибок и отображения информации на UI: в данном случае можно не отображать блок, по которому вернулся ответ с ошибкой, или отображать сообщение. На бэкенде можно использовать паттерны отказоустойчивости, такие как Circuit breaker (автоматический выключатель), чтобы предотвратить каскадные сбои. Также добавление мониторинга и логирования при разработке микросервисов послужило хорошей практикой, поскольку оно позволило отслеживать работу каждого сервиса, выявлять проблемы и оперативно на них реагировать. Настроенные алерты оперативно сообщали ответственным лицам о нездоровье какого-либо микросервиса и о других критических событиях и сбоях.

Третье — зависимость циклов развёртывания: часть обновлений требовалось выполнить в монолите, часть — в микросервисе, а обновления происходят в разное время. Мы передали контроль обновления микросервиса в руки DevOps-команды. Это позволяет нам деплоить сервис независимо от расписания релизов монолита. Также сейчас идём к минимизации внешних технических зависимостей (общие библиотеки, пакеты), чтобы наш микросервис работал и обновлялся изолированно.

В создании микросервисов участвуют специалисты нескольких ключевых направлений. Для успешного запуска требуется слаженная работа аналитиков, разработчиков, тестировщиков, системных администраторов, администраторов БД, DevOps-инженеров. На схеме показаны направления, задействованные в процессе создания микросервиса:

Про шаблоны хочется сказать побольше. Шаблоны — это прекрасно, они очень помогают экономить время и силы. На этапе инициализации сервисов возникает много повторяющейся рутинной работы. Запуск каждого микросервиса «с чистого листа» — это долгий и ресурсоёмкий процесс, который, по сути, представляет собой копирование и адаптацию одних и тех же компонентов из проекта в проект.

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

Процесс создания шаблона включал следующие этапы:

  1. Подготовка пустой заготовки с базовой структурой.

  2. Интеграция необходимых библиотек для управления конфигурациями и версионированием, генерации спецификаций (например, на базе OpenAPI/Swagger).

  3. Добавление базового API‑функционала, включая аутентификацию, работу с сертификатами и электронными подписями.

  4. Добавление базовых консьюмеров для взаимодействия с очередями сообщений.

  5. Подключение обязательных компонент логирования и мониторинга.

  6. Настройка базового CI/CD‑конвейера и определение стандартной архитектуры решения.

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

И тут поджидает импортозамещение…

Здесь слово Максиму Зуеву, системному аналитику из Банковского сопровождения контрактов. Он тоже занимался выделением продукта в микросервис. И в то же время столкнулся ещё и с импортозамещением — и у нас, и у коллег-смежников.

Так сложилось, что параллельно с выделением в микросервис система меняла и СУБД: мы переходили с Microsoft SQL Server на PostgreSQL. И в первое время команде пришлось адаптироваться не только к особенностям микросервисов, но и к отличиям новой СУБД. Какие вопросы нужно было решить? Например: 

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

  • Типизация данных. PostgreSQL и Microsoft SQL Server по-разному работают с некоторыми типами данных, регистром и функциями. Например, в PostgreSQL нет точных аналогов DATETIME2 или NVARCHAR(max), а имена объектов чувствительны к регистру, если не заключены в кавычки. Для дат и времени используются TIMESTAMP и функции вроде NOW() вместо GETDATE(). Синтаксис DDL тоже отличается: IDENTITY в SQL Server заменяется на SERIAL или GENERATED ALWAYS AS IDENTITY в PostgreSQL. При миграции или кроссплатформенной разработке это требует корректировки кода, особенно в запросах с TOP (SQL Server) и LIMIT/OFFSET (PostgreSQL).

  • Таблицы историчности. В Microsoft SQL Server истории изменений данных хранятся в таблицах по умолчанию, а в PostgreSQL их просто нет. Ну что же, мы сделали их сами: написали с помощью триггеров и получили тот же функционал.

  • Производительность. Переписали и оптимизировали средний слой запросов в PostgreSQL, чтобы запросы обрабатывались максимально быстро.

  • Материализованные представления. Вообще здесь есть разные варианты решения: переписать код или сделать табличное представление. Мы в целом пришли к решению от них отказаться.

  • Доступ к данным. В отличие от монолита, где все данные доступны в едином хранилище, микросервисы требуют сложного взаимодействия для получения информации из разных источников. Это создаёт дополнительные задержки и сложности при выполнении запросов. Впрочем, можно держать в своей базе и локальные копии необходимых данных: так они будут в быстром доступе. 

Это ещё не всё. В тот период нам нужно было найти решение по интеграциям с ESB и ETL. С ESB при переходе на микросервисы всё оказалось довольно просто: нужно было лишь переписать end-point-ы, и ESB стала просто получать данные из нового места. Но нам было недостаточно такого решения. Всё-таки мы работаем со смежниками, а значит, нужно быть всегда готовыми к тому, что у них что-то может поменяться, а значит, нам нужно оперативно переработать решение.
 
Мы предусмотрели обратную совместимость: поддерживали как новые end-point-ы в микросервисах, так и старые — в монолите. В этот переходный период гибридной архитектуры шина могла забирать данные из любого места. Позже, когда мы уже были готовы к переключению на микросервис, старые end-point-ы удалили.

В случае с ETL нам нужно было не только развернуть таблицы в новой СУБД и предоставить туда доступ ETL-решению, но и провести череду согласований. Для этого потребовалось дождаться обновления архитектуры, получить различные доступы и создать новые ТУЗы. Кроме того, здесь мы тоже обеспечили обратную совместимость, чтобы ETL-решение забирало данные из MS SQL. Когда подготовили ETL к изменениям, то переключили коннекшен на PostgreSQL и стали забирать данные из нового места.

Что ещё? В монолите можно было получать любые данные одним запросом. После распила монолита данные приходится забирать из одного микросервиса, из второго, третьего… А потом их как-то объединять. Это не всегда просто. В такой ситуации может подойти решение наподобие Data Warehouse, которое могло бы агрегировать все данные. К такому решению мы в будущем придём.

Вжух — и у вас микросервисы (нет)

Какое-то время вы будете жить с гибридной архитектурой. У вас уже появятся микросервисы, выпиленные из монолита или созданные с нуля, но монолит вы ещё продолжите поддерживать параллельно. Таков путь. Что можно посоветовать напоследок тем, кому только предстоит переход на микросервисную архитектуру с монолита?

  • Определите цель перехода. С этого нужно начать и сразу понять, зачем конкретно вам стоит переходить на микросервисы. Потому что модно распиливать монолиты — недостаточно веская причина. А вот если вы правда столкнулись с проблемами масштабирования и отказоустойчивости, если вам нужен гибкий технологический стек или все эти проблемы назрели разом — тогда нет смысла держаться за монолит и стоит правда сменить его на микросервисную архитектуру.

  • Определите объём работ и готовьтесь к тому, что это будет долго. Переход на микросервисы может занимать месяцы, а в каких-то случаях — даже годы.

  • Определите этапы и контрольные точки перехода. Например, стартовый этап — монолит, потом распределённый на модули монолит, а затем это будут уже собственно микросервисы.

  • Поддерживайте работоспособность старых методов. Бывает, что микросервис по какой-то причине не завёлся — и вот тогда можно вернуться к работе этой функциональности в монолите.

  • Заранее переработайте код монолита, распилите его на различные модули, чтобы было легче переносить функциональность.

  • Обеспечьте мониторинг и логирование каждого микросервиса. Нужно будет следить за тем, что каждый микросервис работает и проблем не возникает. Если что-то не так, то, возможно, потребуется развернуть дополнительные экземпляры микросервисов для снижения нагрузки.

  • Ведите документацию. Каждый микросервис должен быть описан: как минимум его назначение, архитектурная схема, интеграции с другими микросервисами.  

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