В этом уроке я хочу не просто перечислить полезные паттерны, а показать саму логику построения API, которое не ломается при повторных запросах. Для платежей, подписок, списаний, начислений и любых критичных действий это особенно важно: сеть может дрогнуть, клиент может повторить запрос, фронт может обновиться, а сервер не должен превращать одну операцию в две или три. Именно поэтому идемпотентность - не “приятная опция”, а базовая защита от дубликатов и хаоса.
Сначала я разбираю, что именно считается уникальной операцией и где должен храниться ее ключ. Потом показываю, как сервер должен вести себя при повторе - возвращать тот же результат, не создавать новую сущность и не запускать повторное списание. Дальше идет практическая часть: где хранить idempotency key, как ставить TTL, как обрабатывать гонки, что делать при частично завершенном запросе, как валидировать одинаковость payload и когда нужно отвечать ошибкой, а не молча продолжать работу. Я специально хочу провести читателя от идеи “повторный запрос - это нормально” к пониманию, что повторный запрос - это штатный сценарий, который надо проектировать заранее.
Еще один важный слой урока - организационный. Хорошая идемпотентность не живет только в коде. Она требует ясного контракта между frontend, backend, очередями, платежным провайдером и логикой ретраев. Если команда это понимает, то сервис становится устойчивее, а инциденты с дублями перестают быть загадкой. Именно это я и хочу донести - не просто как написать защиту, а как встроить ее в архитектуру так, чтобы она реально работала под нагрузкой.
Тут ключевой момент - не путать идемпотентность с простым “не падать дважды”. Настоящая защита начинается с того, что одна и та же бизнес-операция имеет один и тот же смысловой ключ, а повторный вызов не создает новый эффект. Я бы отдельно зафиксировал контракт: какой набор полей образует уникальность, что делать при изменении суммы в повторе и какой ответ сервер возвращает, если операция уже в процессе.
В тестах обязательно нужно имитировать два почти одновременных запроса, сбой после записи в БД и повторную отправку от клиента. Без этого идемпотентность остается только словами.
Я бы еще добавил мониторинг на количество повторов по одному и тому же ключу. Это хороший индикатор того, что либо сеть нестабильна, либо клиентский сценарий сделан неудобно. Когда повторов много, проблема часто не в сервере, а в том, что система слишком легко провоцирует дубли.
Идемпотентный ключ сам по себе тоже чувствителен к безопасности. Его нельзя строить так, чтобы он раскрывал лишнее или позволял угадывать активность пользователя. Минимум данных, строгая привязка к операции, нормальный TTL и отсутствие секретов в логах.
Я бы отслеживал три метрики - количество повторных запросов, процент конфликтов по ключу и долю операций, завершенных не с первого раза. Это сразу показывает, где узкое место: в сети, в логике клиента или в серверной обработке.
Я бы еще советовал хранить состояние операции отдельно от состояния запроса. Тогда проще восстановиться после сбоя, особенно если между записью в БД и ответом клиенту что-то пошло не так.