← Назад к вопросам
Почему некоторые HTTP-методы должны быть идемпотентны?
2.0 Middle🔥 251 комментариев
#API и сетевые протоколы
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Идемпотентность HTTP-методов: архитектурная необходимость
Идемпотентность — свойство операции, при котором повторное выполнение дает тот же результат, что и первое выполнение. Это критическое требование для надежных распределенных систем.
Почему идемпотентность необходима
Проблема в сетевых системах:
- Сетевые сбои — клиент может отправить запрос, но получить timeout
- Автоматические повторы — HTTP клиенты автоматически повторяют failed запросы
- Прокси и балансировщики — промежуточные узлы могут повторять запросы
- Двусмысленность — отправитель не знает, выполнилась ли операция
Без идемпотентности рискуем:
Клиент: "Переведи 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 в распределенных системах.