АрхитектураАрхитектура ПОАрхитектор программного обеспечения

Как распознать, что разделение монолита на сервисы создало распределённый монолит?

Как распознать, что разделение монолита на сервисы создало распределённый монолит?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

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

Исторический контекст

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

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

Постановка проблемы

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

Это создаёт связанность поставки и связанность исполнения. Первая заставляет выпускать компоненты вместе, вторая делает успешный пользовательский запрос зависимым от доступности и задержки длинной цепочки сервисов.

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

Подробное решение

Распределённый монолит распознают по совокупности признаков:

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

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

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

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

У распределённого монолита есть и оправданные временные формы. Например, последовательное выделение компонентов может быть безопасным этапом миграции, если команда заранее понимает, какие зависимости будут устранены и по каким признакам граница станет автономной.

Ситуация из практики

В интернет-магазине сервисы корзины, заказа и расчёта доставки были развернуты отдельно. Оформление заказа синхронно вызывало все три сервиса, а изменение формата адреса требовало одновременного выпуска клиента, корзины и доставки. При недоступности доставки оформление полностью прекращалось.

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

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

Что кандидаты часто упускают

  1. Достаточно ли независимого развертывания, чтобы считать сервис автономным?

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

  1. Всегда ли синхронный вызов означает распределённый монолит?

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

  1. Почему асинхронные события сами по себе не устраняют распределённый монолит?

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