Что такое идемпотентность в REST API и зачем она нужна?

Manual QA Middle API / Backend обновлено 15.11.2025

Краткий ответ

Идемпотентность — это свойство запроса, при котором повторное выполнение даёт тот же результат, что и первое (без побочных эффектов).
В REST API это важно, чтобы повторные запросы не приводили к изменению состояния системы (например, при сетевых сбоях или ретраях).

Пример:

PUT /users/1 с теми же данными можно вызвать 1 или 10 раз — результат один: данные пользователя обновлены, но не продублированы.
А вот POST /users — не идемпотентный, потому что каждый вызов создаёт нового пользователя.

Полный ответ

Что такое идемпотентность

Термин идемпотентность (idempotency) означает, что одинаковый запрос, выполненный несколько раз подряд, не изменяет результат после первого вызова.

То есть система реагирует одинаково — независимо от того, вызвали запрос 1 раз или 100 раз.

В REST API идемпотентность особенно важна для безопасных повторных вызовов — например, если клиент отправил запрос повторно из-за таймаута или сетевого сбоя.

Какие HTTP-методы идемпотентны

Идемпотентные:

  • GET. Только читает данные, не изменяет.
  • HEAD. Возвращает метаданные, без изменений.
  • PUT. Обновляет ресурс (одно и то же состояние при повторе).
  • DELETE. Удаляет один и тот же ресурс (повтор не изменит результат).

Не идемпотентные:

  • POST. Каждый вызов создаёт новый ресурс.

Между двух огней:

  • PATCH. Зависит от реализации (может быть, а может нет).

Примеры

Пример идемпотентного запроса (PUT):

PUT /users/1
Content-Type: application/json{
"name": "Anna",
"age": 25
}

Повторный вызов с теми же данными ничего не изменит — пользователь останется тем же. Результат всегда одинаковый.

Пример неидемпотентного запроса (POST):

POST /users
Content-Type: application/json{
"name": "Anna",
"age": 25
}

Каждый запрос создаёт нового пользователя (id = 101, 102, 103 и т.д.). Результат меняется при каждом вызове.

Пример DELETE

DELETE /users/1

Первый вызов удаляет пользователя.
Второй — ничего не делает, потому что пользователя уже нет. Результат одинаковый — пользователь не существует.

Зачем нужна идемпотентность

Надёжность. Повтор запроса не должен повредить данные.
Пример: Клиент повторно отправил PUT, потому что не получил ответ.

Безопасность. API можно вызывать повторно без страха “дублирования”.
Пример: DELETE можно повторить — ресурс всё равно будет удалён.

Поддержка retry-механизмов. Удобно для интеграций и очередей.
Пример: Retry при сбое сети не создаёт дублей.

Idempotency-Key. В POST-запросах используют ключ, чтобы “зафиксировать” операцию.
Пример: POST /payments с Idempotency-Key: 123 не создаст дубль.

Идемпотентность с ключом (Idempotency-Key)

Когда действие по своей природе не идемпотентно (например, POST /payment),
можно сделать его идемпотентным через ключ:

POST /payments
Idempotency-Key: 123abc
Content-Type: application/json{
"amount": 1000,
"currency": "RUB"
}

Сервер сохранит результат первого вызова и при повторе с тем же ключом вернёт тот же ответ, не создавая новый платёж.

Проверка идемпотентности при тестировании

QA может проверить:

  1. Выполнить запрос 2–3 раза подряд.

  2. Сравнить:

    • статус-коды,

    • тело ответа,

    • состояние БД (не изменилось ли повторно).

Пример теста:

# первый вызов
r1 = requests.put("/users/1", json={"name": "Anna"})
# второй вызов
r2 = requests.put("/users/1", json={"name": "Anna"})
assert r1.status_code == r2.status_code == 200
assert db.get_user(1).name == "Anna"

Типичные ошибки в реализации

PUT создаёт новый ресурс вместо обновления -> неидемпотентно.
DELETE возвращает ошибку 500 при повторе -> должно быть “200 OK” или “404 Not Found”.
POST без Idempotency-Key -> возможны дубликаты при повторных запросах.

Итог

Идемпотентность — это гарантия, что повторный запрос не изменит состояние системы. Она нужна для:

  • надёжности при сбоях,

  • безопасных ретраев,

  • предсказуемого поведения API.

Если совсем по-простому:

PUT и DELETE можно вызвать несколько раз без последствий,
POST — нельзя (если не использовать Idempotency-Key).

Оцените ответ
0 / 5 · 0