Содержание

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

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

Чем монолит отличается от микросервисов?

Монолит — это одно приложение с общей кодовой базой и обычно одной базой данных, которое собирается и выкладывается целиком. Микросервисы — набор независимых приложений, каждое со своей зоной ответственности, своими данными и своим циклом выкладки; общаются они по сети через API или очереди сообщений.

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

Критерий Монолит Микросервисы
Скорость старта Высокая Низкая: нужна инфраструктура с первого дня
Выкладка Всё приложение целиком Каждый сервис отдельно
Транзакции Обычные, в одной базе Распределённые: саги, компенсации
Отладка Один лог, один стектрейс Трассировка запросов между сервисами
Масштабирование Всё приложение сразу Только нагруженные сервисы
Независимость команд Общий код и релиз Каждая команда владеет своими сервисами
Стоимость инфраструктуры Минимальная Выше: оркестрация, мониторинг, сеть

Почему микросервисы на старте замедляют продукт?

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

Что ещё появляется с первого дня:

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

Всё это нужно, чтобы система вообще работала, но ничего из этого не приближает продукт к пользователю. Тот же вывод есть и в разборе выбора бэкенда для MVP: микросервисы на старте — одна из самых дорогих ошибок.

Серверное оборудование с подключёнными кабелями в дата-центре Фото: Unsplash

Когда микросервисы действительно оправданы?

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

Признаки, что отдельный сервис окупится:

  1. Над продуктом работают несколько команд, и общий релиз стал узким местом.
  2. Одна часть системы нагружена на порядок сильнее остальных — например, обработка медиа или расчёт рекомендаций.
  3. У части системы другие требования к надёжности или безопасности — платёжный контур, обработка персональных данных.
  4. Часть логики удобнее писать на другом стеке: машинное обучение на Python рядом с бэкендом на PHP.

Даже тогда выносят не всё сразу, а один сервис с понятной границей, и смотрят, окупается ли это.

Что такое модульный монолит и почему он хорош на старте?

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

Пример структуры бэкенда, где каждый модуль отвечает за свою область:

app/
  Modules/
    Catalog/      — товары, категории, цены
      Contracts/  — публичный интерфейс модуля
      Models/
      Services/
    Orders/       — корзина, заказы, статусы
    Payments/     — платежи, вебхуки, возвраты
    Users/        — регистрация, профили, роли

Правила, которые держат границы

Граница модуля держится, только если её проверяют автоматически. Договорённость «не лезть в чужие модели» нарушится при первом дедлайне.

  • модули общаются через интерфейсы или события, а не через чужие модели и таблицы;
  • у каждой таблицы один модуль-владелец, остальные читают данные через его API;
  • зависимости между модулями проверяются в CI архитектурными тестами;
  • события внутри монолита оформляются так же, как будут выглядеть сообщения в очереди.
// Заказ не лезет в таблицу платежей, а спрашивает модуль платежей через контракт
final class OrderService
{
    public function __construct(private PaymentGateway $payments) {}

    public function pay(Order $order): PaymentStatus
    {
        return $this->payments->charge($order->id, $order->total);
    }
}

Если позже модуль платежей станет отдельным сервисом, реализация PaymentGateway сменится с вызова функции на HTTP-запрос, а код заказов останется прежним.

Как понять, что пора разделять монолит?

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

Что обычно становится поводом:

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

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

Коротко

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

Частые вопросы

  • Выдержит ли монолит большую нагрузку?

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

  • Нужен ли Kubernetes для микросервисов?

    Не обязательно: несколько сервисов можно запустить через Docker Compose или управляемые облачные сервисы. Kubernetes оправдан, когда сервисов много и нужны автоматическое масштабирование и самовосстановление.

  • Сколько сервисов должно быть в микросервисной архитектуре?

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

  • Можно ли разделить базу данных монолита постепенно?

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