Дублирующиеся отправки API: спроектируйте контракт идемпотентности до добавления ключа
Определите вызывающего, полезный груз, атомарную заявку и политику воспроизведения, чтобы повтор имел предсказуемый результат приложения.
В этом материале
Короткий ответ
При повторной отправке заказа используйте один ключ для одного и того же аутентифицированного вызывающего и одного и того же логического полезного груза. Приложение может атомарно заявить о этой комбинации и сохранить завершенный результат для повторного воспроизведения. Измененный полезный груз с тем же ключом должен получить задокументированный конфликт приложения. Уникальная строка базы данных координирует заявки; она сама по себе не гарантирует, что платеж, электронное письмо или иное внешнее действие произойдет ровно один раз.
Разделяйте семантику HTTP и контракт приложения
Идемпотентная операция имеет одинаковый предполагаемый эффект на сервере при повторении, как при однократном выполнении. Ответы не обязаны быть идентичными для выполнения этого определения. Точка POST не приобретает безопасный контракт повтора лишь потому, что клиент отправляет дополнительный заголовок. Сервер должен реализовать и задокументировать, как он интерпретирует этот ключ, какие операции он покрывает и как повторы взаимодействуют с аутентификацией и сохраненным состоянием.
Ограничьте ключ доверенному вызывающему
В гипотетическом примере ключ k1 принадлежит одному аутентифицированному вызывающему и одной операции создания заказа. Другой вызывающий, использующий k1, не должен получать результат первого вызывающего. Получайте идентичность вызывающего из доверенной аутентификации, а не из свободно подаваемого поля полезного груза. Определите, разделяют ли ключи пространство имен между операциями или привязаны к конкретной конечной точке, и сохраните эту область в правиле уникальности базы данных.
Задайте эквивалентность полезного груза
Один и тот же ключ должен представлять одну и ту же логическую отправку. Определите, какие поля являются частью операции и как приложение сравнивает их, например, используя каноническое представление или дайджест по задокументированному правилу. Сырые байты JSON могут отличаться, но представлять одни и те же данные, поэтому равенство байтов является выбором политики. И наоборот, исключение важного поля, такого как сумма, может неправильно идентифицировать разные заказы как эквивалентные.
Разберите построенный повтор
Предположим, вызывающий A отправляет ключ k1 с полезным грузом, запрашивающим две единицы товара X. Первый успешный запрос сохраняет результат заказа. Повтор A с k1 и эквивалентным полезным грузом возвращает сохраненный результат согласно этому предложенному контракту. Запрос с k1, но запрашивающий три единицы, отклоняется как несовпадение полезного груза. Это иллюстрация проектирования, а не утверждение, что каждый существующий API использует тот же код состояния или поведение воспроизведения.
Заявляйте ключ атомарно
Последовательность проверка-затем-вставка может состязаться: два запроса могут увидеть отсутствие существующего ключа. Ограничение уникальности базы данных по выбранному вызывающему, операции и области ключа может обеспечить единственную сохраненную заявку. PostgreSQL INSERT ON CONFLICT может помочь координировать вставку. Приложение все еще должно интерпретировать, владеет ли оно новой заявкой или нашло существующую, и не должно позволять каждому проигравшему запросу выполнять защищенную операцию в любом случае.
Представляйте состояния обработки и завершения
Сохраняйте достаточно состояния, чтобы различать запрос, который еще обрабатывается, и тот, чей результат можно воспроизвести. Определите, как отвечает конкурентный повтор, пока первая попытка выполняется: ожидание, задокументированный временный ответ или инструкция повтора - возможные политики. Сохраняйте завершенное состояние и его результат согласованно с изменениями базы данных, где это возможно. Не считайте наличие любой строки ключа доказательством успешного завершения заказа.
Обрабатывайте внешние эффекты и окна сбоев
Провайдер платежей или служба электронной почты не автоматически участвуют в транзакции базы данных. Сбой после внешнего эффекта, но до сохранения завершенного результата, создает проблему восстановления. Долговечная очередь вывода, дедупликация, поддерживаемая провайдером, или явная реконcilиация могут составить часть решения в зависимости от эффекта. У каждого своя собственная контрактная система. Простое обертывание локальной вставки ключа в транзакцию не устанавливает поведение ровно один раз между системами.
Задокументируйте лимиты удержания и восстановления
Опишите, как долго ключи и результаты удерживаются, что возвращается при воспроизведении и что происходит после истечения срока. Удаление записи может позволить последующему запросу с тем же ключом стать новой отправкой. Также определите восстановление для заброшенных состояний обработки и можно ли повторно использовать неудачные попытки. Эти выборы влияют и на корректность, и на хранение. Клиент должен уметь различать безопасный повтор и создание новой логической операции.
Что проверить
- Используйте ключ только для одной и той же логической операции.
- Ограничивайте поиск аутентифицированным вызывающим.
- Определите эквивалентность полезного груза и поведение при несовпадении.
- Сделайте заявку атомарной и уникальной.
- Продумайте восстановление внешних эффектов и истекших ключей.
Границы применения
Это руководство описывает гипотетический контракт приложения, а не полную серверную реализацию или универсальный стандарт Idempotency-Key. Уникальность базы данных и HTTP-идемпотентность сами по себе не гарантируют внешние побочные эффекты ровно один раз.