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

Команда выделила сервис из монолита, но его операции требуют общей транзакции с соседним сервисом. Какой ар...

Команда выделила сервис из монолита, но его операции требуют общей транзакции с соседним сервисом. Какой архитектурный сигнал показывает, что границу выделили неверно?

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

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

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

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

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

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

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

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

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

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

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

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

Основные варианты следующие:

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

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

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

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

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

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

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

  1. Всегда ли общая транзакция между сервисами означает, что их нужно немедленно объединить?

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

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

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

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

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

  1. Как отличить допустимую событийную задержку от нарушения бизнес-инварианты?

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

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