Перейти к основному содержимому

Обеспечение идемпотентности API

Идемпотентная операция — это операция, которая при многократном вызове возвращает один и тот же результат. То есть, если мы повторно вызываем идемпотентный метод API, состояние ресурса меняться не будет.

По-хорошему, любой API должен быть идемпотентным.

Идемпотентность в REST API

HTTP-методы GET, PUT, DELETE формально считаются идемпотентными, тогда как POST и PATCH нет. Это не означает, что нельзя сделать GET неидемпотентным, а POST идемпотентным.

Важно: ответ идемпотентного метода может меняться. Например, при повторном вызове идемпотентного API создания заказа — заказ не будет создаваться ещё раз, но API может ответить как 200, так и 400. При обоих кодах ответа API будет идемпотентно с точки зрения состояния сервера (заказ один, с ним ничего не происходит), а с точки зрения клиента поведение существенно разное.

Проблемы неидемпотентных API

Проблема №1. Дублирование операций

При сетевых задержках или ошибках одна и та же операция может быть выполнена несколько раз.

Пример с заказом такси:

  1. Пользователь вызывает такси в приложении
  2. Приложение отправляет запрос на создание заказа на сервер
  3. Возникает сбой, и приложение не получает успешный ответ по таймауту
  4. Приложение показывает сообщение «произошла ошибка» и делает кнопку заказа снова активной
  5. Пользователь снова нажимает кнопку
  6. К пользователю может приехать два такси вместо одного

Проблема №2. Неконсистентное состояние

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

Пример с заказом такси:

  1. Пользователь изменяет пункт назначения в активном заказе такси через приложение.
  2. Приложение отправляет запрос PATCH /v1/orders/{id} на сервер для обновления заказа
  3. Возникает сбой, и приложение не получает подтверждение об успешном обновлении
  4. Приложение показывает пользователю сообщение об ошибке и предлагает повторить попытку
  5. Пользователь решает изменить пункт назначения на новый и снова отправляет запрос
  6. Сервер обрабатывает оба запроса: сначала второй, затем первый. В системе возникает неконсистентность: водитель видит один пункт назначения, а пользователь ожидает, что будет доставлен в другое место.

Идемпотентность создания и изменения

Способы обеспечения идемпотентности методов создания (POST) и частичного изменения (PATCH):

  1. Ключ идемпотентности в заголовке запросов
  2. Версионирование состояния
  3. Блокировка на основе правил

Ключ идемпотентности

Это уникальный идентификатор операции, который помогает защититься от повторного исполнения операции.

Как это работает:

  1. Клиент генерирует уникальный ключ идемпотентности и отправляет его в заголовке запроса. Например, Idempotency-Key:
  2. Сервер проверяет, был ли уже обработан запрос с таким ключом. Если да, возвращает результат предыдущего запроса, не выполняя операцию повторно. Иначе просто выполняет операцию.

Версионирование состояния

Сервер отслеживает версии состояния ресурса и позволяет клиенту указывать, для какой версии предназначен запрос. Если состояние изменилось, сервер отклоняет запрос. Версия может быть как числом (номером последнего изменения), так и хэшом от списка ресурсов.

Как это работает на примере сервиса заказа такси:

Вводные:

  • Каждый раз, когда в системе происходит изменение (создание, отмена, редактирование заказа), сервер увеличивает версию списка заказов. Эта версия отражает все изменения, произошедшие с заказами пользователя.
  • Перед созданием нового заказа приложение запрашивает у сервера текущую версию списка заказов через GET /v1/orders.

Процесс:

  1. При создании заказа приложение включает в запрос заголовок If-Match с версией, которую оно знает.
  2. Сервер проверяет, соответствует ли версия в запросе текущему состоянию списка заказов. Если версия не совпадает, сервер отклоняет запрос, предотвращая создание дубликата.
  3. Если состояние заказов изменилось (например, заказ был отменен), сервер сообщает об этом приложению, отправляя ошибку с предложением перезагрузить информацию о заказах через GET /v1/orders.

Идемпотентность удаления

HTTP-метод удаления DELETE по своей природе идемпотентный, так как повторное удаление уже удаленного ресурса не изменяет состояние системы. После первого успешного все последующие запросы на удаление будут возвращать ошибку 404 (или другой код согласно логике сервера).

Более гибкий способ обеспечить идемпотентность удаления – использовать подход “Soft Delete”. Вместо физического удаления записи из базы данных, запись просто помечается как удаленная с помощью специального флага.

Soft Delete гарантирует, что все последующие запросы на удаление будут успешными и вернут одинаковый результат. Это также позволяет отслеживать удаленные записи и при необходимости восстанавливать их, обеспечивая дополнительный уровень безопасности и контроля.

Статьи

  1. Стажёр Вася и его истории об идемпотентности API — очень рекомендуем
  2. Кратко об идемпотентности от Yandex Cloud
  3. Идемпотентность: больше, чем кажется
  4. Идемпотентность при использовании API Mindbox
  5. Что такое ключ идемпотентности и зачем он нужен
  6. Семантика exactly-once в Apache Kafka

Видео

  1. Что такое идемпотентность, или история Васи и его приложения
  2. Идемпотентность и коммутативность API в очередях и HTTP // Демо-занятие курса «Software Architect»
  3. Идемпотентность: что, где и как
  4. Микросервисы: идемпотентность операций