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