Обеспечение идемпотентности API
Идемпотентная операция — это операция, которая при многократном вызове возвращает один и тот же результат. То есть, если мы повторно вызываем идемпотентный метод API, состояние ресурса меняться не будет.
По-хорошему, любой API должен быть идемпотентным.
Идемпотентность в REST API
HTTP-методы GET, PUT, DELETE формально считаются идемпотентными, тогда как POST и PATCH нет. Это не означает, что нельзя сделать GET неидемпотентным, а POST идемпотентным.
Важно: ответ идемпотентного метода может меняться. Например, при повторном вызове идемпотентного API создания заказа — заказ не будет создаваться ещё раз, но API может ответить как 200, так и 400. При обоих кодах ответа API будет идемпотентно с точки зрения состояния сервера (заказ один, с ним ничего не происходит), а с точки зрения клиента поведение существенно разное.
Проблемы неидемпотентных API
Проблема №1. Дублирование операций
При сетевых задержках или ошибках одна и та же операция может быть выполнена несколько раз.
Пример с заказом такси:
- Пользователь вызывает такси в приложении
- Приложение отправляет запрос на создание заказа на сервер
- Возникает сбой, и приложение не получает успешный ответ по таймауту
- Приложение показывает сообщение «произошла ошибка» и делает кнопку заказа снова активной
- Пользователь снова нажимает кнопку
- К пользователю может приехать два такси вместо одного
Проблема №2. Неконсистентное состояние
Неидемпотентные операции затрудняют отслеживание текущего состояния системы, что может привести к ошибкам и неконсистентности данных.
Пример с заказом такси:
- Пользователь изменяет пункт назначения в активном заказе такси через приложение.
- Приложение отправляет запрос PATCH /v1/orders/{id} на сервер для обновления заказа
- Возникает сбой, и приложение не получает подтверждение об успешном обновлении
- Приложение показывает пользователю сообщение об ошибке и предлагает повторить попытку
- Пользователь решает изменить пункт назначения на новый и снова отправляет запрос
- Сервер обрабатывает оба запроса: сначала второй, затем первый. В системе возникает неконсистентность: водитель видит один пункт назначения, а пользователь ожидает, что будет доставлен в другое место.
Идемпотентность создания и изменения
Способы обеспечения идемпотентности методов создания (POST) и частичного изменения (PATCH):
- Ключ идемпотентности в заголовке запросов
- Версионирование состояния
- Блокировка на основе правил
Ключ идемпотентности
Это уникальный идентификатор операции, который помогает защититься от повторного исполнения операции.
Как это работает:
- Клиент генерирует уникальный ключ идемпотентности и отправляет его в заголовке запроса. Например, Idempotency-Key:
- Сервер проверяет, был ли уже обработан запрос с таким ключом. Если да, возвращает результат предыдущего запроса, не выполняя операцию повторно. Иначе просто выполняет операцию.
Версионирование состояния
Сервер отслеживает версии состояния ресурса и позволяет клиенту указывать, для какой версии предназначен запрос. Если состояние изменилось, сервер отклоняет запрос. Версия может быть как числом (номером последнего изм енения), так и хэшом от списка ресурсов.
Как это работает на примере сервиса заказа такси:
Вводные:
- Каждый раз, когда в системе происходит изменение (создание, отмена, редактирование заказа), сервер увеличивает версию списка заказов. Эта версия отражает все изменения, произошедшие с заказами пользователя.
- Перед созданием нового заказа приложение запрашивает у сервера текущую версию списка заказов через GET /v1/orders.
Процесс:
- При создании заказа приложение включает в запрос заголовок If-Match с версией, которую оно знает.
- Сервер проверяет, соответствует ли версия в запросе текущему состоянию списка заказов. Если версия не совпадает, сервер отклоняет запрос, предотвращая создание дубликата.
- Если состояние заказов изменилось (например, заказ был отменен), сервер сообщает об этом приложению, отправляя ошибку с предложением перезагрузить информацию о заказах через GET /v1/orders.