АрхитектураПроектирование системАрхитектор программных систем

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

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

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

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

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

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

Идея выделять сервисы по бизнес-границам сформировалась как реакция на монолиты, в которых любое изменение затрагивало большую часть системы. Подходы Domain-Driven Design и микросервисная архитектура предложили разделять систему по предметным областям и зонам ответственности, чтобы команды могли независимо разрабатывать и поставлять изменения.

Проблема в том, что механическое дробление монолита не создаёт автономность. Если части системы постоянно меняются вместе, их физическое разделение лишь переносит связанность в сетевые вызовы, контракты и процессы релиза.

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

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

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

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

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

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

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

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

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

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

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

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

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

1. Достаточно ли того, что компонент имеет отдельную бизнес-функцию, чтобы сделать его сервисом?

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

2. Всегда ли частые совместные релизы доказывают неправильную декомпозицию?

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

3. Как понять, что распределённая граница оправдана несмотря на сильную связанность?

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