АрхитектураМикросервисы и интеграцииРазработчик микросервисов

Синхронный API возвращает один и тот же код ошибки для временного сбоя или нарушения бизнес правила. Какой ...

Синхронный API возвращает один и тот же код ошибки для временного сбоя или нарушения бизнес-правила. Какой дефект интеграционного контракта это создаёт?

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

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

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

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

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

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

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

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

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

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

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

Минимально полезное разделение выглядит так:

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

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

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

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

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

Сервис оформления заказа получает от сервиса резервирования одинаковый ответ при отсутствии товара и при временной недоступности хранилища резервов. Рассматривались три варианта.

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

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

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

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

  1. Достаточно ли разделить ошибки только на «4xx» и «5xx»?

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

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

  1. Можно ли всегда повторять запрос, если ошибка помечена как временная?

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

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

  1. Почему нельзя считать отсутствие ответа тем же, что и явный технический отказ?

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

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