Вебхук отвечает 2xx до записи события в надёжное хранилище, после чего процесс падает. Какой контракт ответа должен проверять интеграционный тест?
Интеграционный тест должен проверять, что вебхук возвращает успешный ответ только после надёжной фиксации события, достаточной для последующей обработки. Если процесс может упасть между отправкой 2xx и сохранением события, успешный ответ преждевременен: отправитель, скорее всего, не повторит доставку, а событие будет потеряно.
Вебхуки работают поверх ненадёжной доставки: соединение может оборваться, получатель может быть временно недоступен, а процесс обработки — завершиться в любой момент. Поэтому отправитель обычно использует код ответа как подтверждение: успешный ответ означает, что событие принято, а ошибка или тайм-аут допускают повторную доставку.
Так появился практический принцип acknowledge after durable acceptance: сначала принять событие в устойчивое хранилище или надёжную очередь, затем подтвердить его получение отправителю. Этот подход отделяет надёжность приёма от длительности бизнес-обработки.
Если обработчик сначала отвечает 2xx, а затем записывает событие, возникает окно потери. При падении процесса отправитель считает доставку успешной и прекращает повторы, хотя получатель фактически не сохранил данные.
Проверка только HTTP-кода не выявляет такой дефект. Нужна интеграционная проверка порядка действий: успешный ответ допустим лишь после подтверждённой фиксации события в устойчивом хранилище.
Тест должен имитировать сбой между отправкой ответа и попыткой сохранения либо проверять наблюдаемое поведение при отказе хранилища. Если хранилище недоступно, обработчик должен вернуть неуспешный ответ или завершить запрос с тайм-аутом, не выдавая преждевременное подтверждение.
При успешной обработке сначала создаётся устойчивая запись о принятом событии, затем возвращается 2xx. Дальнейшая бизнес-обработка может выполняться синхронно или асинхронно, но событие уже не должно зависеть от жизни текущего процесса.
Такой контракт не гарантирует, что событие будет обработано ровно один раз. Отправитель всё ещё может повторить доставку из-за сетевого сбоя после фиксации, но получатель должен переносить повторы безопасно — например, с помощью идентификатора события и дедупликации.
Есть и компромисс: ответ после записи в очередь или базу данных обычно увеличивает задержку подтверждения. Однако это предпочтительнее потери события; длительную бизнес-логику не следует выполнять до ответа, если отправитель ожидает быстрый приём.
Сервис платежей отправляет вебхук о проведённой оплате. Обработчик сразу возвращал 200, а затем публиковал событие во внутреннюю очередь. При перезапуске контейнера между этими действиями платежи иногда не доходили до сервиса начисления бонусов.
Рассматривались варианты: увеличить тайм-аут отправителя, повторять публикацию в памяти процесса или сначала записывать событие в базу данных. Увеличение тайм-аута не устраняло падение процесса, а память не переживала перезапуск; запись в устойчивое хранилище добавляла небольшую задержку и требовала последующей очистки обработанных записей.
Выбрали фиксацию входящего события в базе до возврата 2xx, после чего отдельный обработчик публиковал его во внутреннюю очередь. Интеграционный тест проверял отказ базы, отсутствие успешного ответа при таком отказе и возможность повторной доставки после временной ошибки. Потери событий прекратились, а повторы стали безопасными благодаря идентификатору события.
Нет, не обязательно. Если событие уже надёжно принято, возврат ошибки может вызвать повторную доставку; это допустимо только при идемпотентной обработке. Обычно после устойчивого приёма возвращают 2xx, а сбои дальнейшей обработки решают повторной обработкой из очереди, отложенными попытками или карантином сообщений.
Оперативная память теряется при перезапуске, аварийном завершении процесса или переносе экземпляра на другой узел. Подтверждение должно следовать за фиксацией в ресурсе, который сохраняется независимо от жизни конкретного обработчика и имеет определённые гарантии надёжности.
Не обязательно. На практике надёжные системы часто выбирают модель at-least-once, при которой возможны повторы, но не должна происходить потеря после подтверждённого приёма. Интеграционный тест должен проверять сохранность события и безопасное повторение, а не недостижимую без дополнительных механизмов гарантию ровно одной обработки.