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

Почему некоторые HTTP-методы должны быть идемпотентны?

2.0 Middle🔥 251 комментариев
#API и сетевые протоколы

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

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

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

Идемпотентность HTTP-методов: архитектурная необходимость

Идемпотентность — свойство операции, при котором повторное выполнение дает тот же результат, что и первое выполнение. Это критическое требование для надежных распределенных систем.

Почему идемпотентность необходима

Проблема в сетевых системах:

  1. Сетевые сбои — клиент может отправить запрос, но получить timeout
  2. Автоматические повторы — HTTP клиенты автоматически повторяют failed запросы
  3. Прокси и балансировщики — промежуточные узлы могут повторять запросы
  4. Двусмысленность — отправитель не знает, выполнилась ли операция

Без идемпотентности рискуем:

Клиент: "Переведи 100 рублей"
Сервер: Выполнил, но ответ потерялся
Клиент: Timeout, повтор запроса
Сервер: Выполнил ещё раз
Результат: Перевод выполнен ДВАЖДЫ!

Какие методы должны быть идемпотентны

GET — получение данных:

GET /api/users/123
GET /api/users/123 // повтор — нет побочных эффектов

PUT — обновление ресурса целиком:

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

// При повторе результат одинаков
PUT /api/users/123
{ name: "John", age: 30 }

DELETE — удаление ресурса:

DELETE /api/users/123 // Успех
DELETE /api/users/123 // Уже удалён, но статус 200 или 404 — OK

OPTIONS, HEAD — безопасные информационные методы

Методы, которые НЕ идемпотентны

POST — создание нового ресурса:

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

// Первый: создан пользователь ID 1
// Повтор: создан пользователь ID 2
// Результат: два одинаковых пользователя!

PATCH — частичное обновление:

PATCH /api/user/123
{ "increment_balance": 100 }

// Первый: 1000 → 1100
// Повтор: 1100 → 1200 (неправильно!)

Как обеспечить идемпотентность

1. Уникальные идентификаторы запросов:

const idempotencyKey = crypto.randomUUID();

POST /api/transfer
{ "from": "123", "to": "456", "amount": 100 }
Headers: { "Idempotency-Key": idempotencyKey }
const cache = new Map();

app.post("/api/transfer", (req, res) => {
  const key = req.headers["idempotency-key"];
  
  if (cache.has(key)) {
    return res.json(cache.get(key));
  }
  
  const result = processTransfer(req.body);
  cache.set(key, result);
  res.json(result);
});

2. Проверка состояния перед операцией:

PUT /api/users/123
{ name: "John", version: 5 }

const user = await db.getUser(123);
if (user.version !== 5) {
  return res.status(409).json({ error: "Conflict" });
}

3. Генерация ID на сервере:

POST /api/orders
{ "client_generated_id": "order-20240101-001" }

const existing = await db.getOrder({ client_generated_id });
if (existing) {
  return res.json(existing);
}
const order = await db.createOrder(...);
res.json(order);

Пример в Express

const express = require("express");
const app = express();
const idempotencyCache = new Map();

const idempotencyMiddleware = (req, res, next) => {
  if (["POST", "PATCH", "DELETE"].includes(req.method)) {
    const key = req.headers["idempotency-key"];
    
    if (!key) {
      return res.status(400).json({ 
        error: "Idempotency-Key header required" 
      });
    }
    
    if (idempotencyCache.has(key)) {
      const cached = idempotencyCache.get(key);
      return res.status(cached.statusCode).json(cached.body);
    }
    
    const originalJson = res.json.bind(res);
    res.json = function(body) {
      idempotencyCache.set(key, {
        statusCode: res.statusCode,
        body: body
      });
      return originalJson(body);
    };
  }
  next();
};

app.use(idempotencyMiddleware);

app.post("/api/transfer", (req, res) => {
  res.json({ success: true, id: "txn-123" });
});

Лучшие практики

  • GET, PUT, DELETE — всегда должны быть идемпотентными
  • POST — новое создание, используй Idempotency-Key
  • PATCH — проверяй условия обновления перед применением
  • Кэширование результатов — по Idempotency-Key с TTL
  • Версионирование — используй version/timestamp для конфликтов
  • Логирование — фиксируй повторы для аудита и анализа

Идемпотентность — не опция, а требование надежного API в распределенных системах.

Почему некоторые HTTP-методы должны быть идемпотентны? | PrepBro