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