← Назад к вопросам

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

1.8 Middle🔥 161 комментариев
#API и интеграции#Архитектура систем

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Идемпотентность метода

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

Определение

Математически: операция f идемпотентна, если f(f(x)) = f(x).

Для REST API: повторное выполнение запроса с идентичными параметрами не должно менять результат или состояние системы.

Почему это важно

В распределённых системах всегда есть вероятность:

  • Network failures — запрос отправлен, но ответ потерялся
  • Timeouts — клиент не знает, выполнился запрос или нет
  • Retry logic — приложение повторяет запрос
  • Дублирование — сообщение обработано дважды из-за сбоя

Идемпотентность гарантирует, что при повторном выполнении проблем не будет.

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

МетодИдемпотентенОписание
GET✅ ДаТолько читает, не изменяет
HEAD✅ ДаКак GET, но без тела ответа
OPTIONS✅ ДаПолучает информацию о методах
PUT✅ ДаЕсли выполнить дважды с теми же данными — результат одинаковый
DELETE✅ ДаУдалить дважды то же самое — состояние одинаковое
POST❌ НетКаждый вызов создаёт новый ресурс
PATCH❌ Нет*Зависит от реализации

*PATCH может быть идемпотентным, если правильно спроектирован.

Примеры идемпотентных операций

1. GET запрос

GET /api/users/123

Первый запрос: {"id": 123, "name": "John"}
Второй запрос: {"id": 123, "name": "John"}
Второй запрос с timeout: {"id": 123, "name": "John"}

РЕЗУЛЬТАТ: Одинаковый ✅

2. DELETE запрос

DELETE /api/users/123

Первый запрос: 204 No Content (удалено)
Второй запрос: 404 Not Found (уже удалено)

РЕЗУЛЬТАТ: Состояние системы одинаковое (пользователь удалён) ✅
Идемпотентен, потому что конечное состояние — объект удалён

3. PUT запрос (идемпотентный)

PUT /api/users/123 {"name": "John", "age": 30}

Первый запрос: 200 OK, пользователь обновлён
Второй запрос: 200 OK, пользователь снова обновлён теми же данными
Третий запрос: 200 OK, результат не изменился

РЕЗУЛЬТАТ: Данные в БД одинаковые ✅

Примеры НЕидемпотентных операций

1. POST запрос (создание)

POST /api/users {"name": "John"}

Первый запрос: 201 Created, создан пользователь ID=1
Второй запрос: 201 Created, создан новый пользователь ID=2

РЕЗУЛЬТАТ: В системе уже два разных пользователя ❌

2. POST запрос (операция со счётом)

POST /api/accounts/123/withdraw {"amount": 100}

Первый запрос: 200 OK, счёт уменьшен на 100
Второй запрос: 200 OK, счёт снова уменьшен на 100

РЕЗУЛЬТАТ: Снялось 200 вместо 100 ❌

Как сделать операцию идемпотентной

Подход 1: Idempotency Key

Клиент отправляет уникальный ключ, сервер запоминает какие запросы он обработал:

POST /api/payments
Idempotency-Key: "550e8400-e29b-41d4-a716-446655440000"
Content-Type: application/json

{"amount": 100, "currency": "USD"}

# Первый запрос → платёж обработан, результат сохранён
# Второй запрос с тем же ключом → возвращается кэшированный результат
# Третий запрос с другим ключом → новый платёж

Преимущества:

  • Работает с любыми операциями
  • Стандартизовано (используется в Stripe, PayPal)

Реализация:

# На сервере
class IdempotencyStore:
    def __init__(self):
        self.requests = {}  # {idempotency_key: result}
    
    def get_or_execute(self, key, operation):
        if key in self.requests:
            return self.requests[key]  # вернуть кэшированный результат
        result = operation()  # выполнить операцию
        self.requests[key] = result  # сохранить результат
        return result

Подход 2: Уникальные ограничения в БД

Сделать операцию идемпотентной через уникальные констрейнты:

CREATE TABLE orders (
    id UUID PRIMARY KEY,
    external_order_id VARCHAR UNIQUE,  -- Уникальный идентификатор от клиента
    user_id UUID,
    amount DECIMAL,
    created_at TIMESTAMP
);
# На сервере
def create_order(user_id, external_order_id, amount):
    try:
        # Если запрос повторится, будет нарушение UNIQUE
        order = db.create_order(
            id=generate_uuid(),
            user_id=user_id,
            external_order_id=external_order_id,
            amount=amount
        )
        return order
    except IntegrityError:  # UNIQUE violation
        # Заказ уже существует, вернуть существующий
        return db.get_order(external_order_id=external_order_id)

Подход 3: Переиспользование ресурса (PUT вместо POST)

# Вместо:
POST /api/users {"id": 123, "name": "John"}  # Может создать дубль

# Использовать:
PUT /api/users/123 {"name": "John"}  # Идемпотентно

Рекомендации для System Analyst

При проектировании API

  1. GET, PUT, DELETE — всегда идемпотентны, используй их
  2. POST — для создания и non-idempotent операций
  3. PATCH — если идемпотентен (например, merge patch) — указать в docs

При работе с платежами

  • ВСЕГДА используй Idempotency Key
  • Сохраняй результаты на час-день (в зависимости от требований)
  • Документируй это в API

При обработке асинхронных очередей

  • Сообщения могут быть обработаны дважды
  • Обработчики должны быть идемпотентными
  • Используй Idempotency Key или деловые ключи

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

def test_payment_idempotency():
    idempotency_key = "test-key-123"
    
    # Первый запрос
    response1 = client.post(
        "/payments",
        json={"amount": 100},
        headers={"Idempotency-Key": idempotency_key}
    )
    assert response1.status_code == 200
    payment_id_1 = response1.json()["id"]
    
    # Второй запрос с тем же ключом
    response2 = client.post(
        "/payments",
        json={"amount": 100},
        headers={"Idempotency-Key": idempotency_key}
    )
    assert response2.status_code == 200
    payment_id_2 = response2.json()["id"]
    
    # ID должны быть одинаковыми
    assert payment_id_1 == payment_id_2

Заключение

Идемпотентность — это не опция, а требование для надёжных систем:

  • Сетевые сбои — неизбежны
  • Retry logic — неизбежна
  • Идемпотентность — защита от двойной обработки

При проектировании API всегда спроси себя: "Что произойдёт, если этот запрос выполнится дважды?" Если ответ "ничего плохого" — значит идемпотентен.