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