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

Ответ

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

Ключевой момент: Идемпотентность — о состоянии системы, а не о коде ответа. Ответ сервера (например, статус 404 после первого удаления и 200 после второго) может отличаться, но итоговое состояние ресурса на сервере должно быть таким же.

Классификация HTTP-методов с точки зрения идемпотентности:

Метод Идемпотентен? Почему Пример для REST API (Node.js + Express)
GET Да Только получение данных, не изменяет состояние. app.get('/users/:id', handler) — многократные вызовы не создадут новых пользователей.
PUT Да Полная замена ресурса. Повторный запрос с теми же данными оставит ресурс в том же конечном состоянии. app.put('/users/:id', handler) — обновит пользователя. Второй идентичный запрос не изменит результат.
DELETE Да Удаление ресурса. После первого успешного удаления ресурса его больше нет, последующие запросы также вернут 404 или 410, но состояние системы (отсутствие ресурса) не изменится. app.delete('/users/:id', handler)
POST Нет Создание нового ресурса. Каждый вызов обычно создает новый объект, изменяя состояние системы. app.post('/users', handler) — каждый запрос создаст нового пользователя с новым ID.
PATCH Нет (чаще всего) Частичное обновление. Может быть идемпотентным, если операция сама по себе идемпотентна (например, установка поля в конкретное значение), но в общем случае — нет. Неидентичный PATCH: { "op": "increment", "path": "/views", "value": 1 }

Зачем это важно в Node.js бэкенде? Для обеспечения надежности. Сетевые сбои или таймауты могут приводить к повторной отправке запросов клиентом. Идемпотентные операции (PUT, DELETE) безопасны для такого повтора. Для неидемпотентных (POST) нужно реализовывать механизмы предотвращения дублирования (idempotency keys).

Ответ 18+ 🔞

А, идемпотентность! Ну это, блядь, классика, про которую каждый второй джуниор на собеседовании нихуя не может внятно объяснить, а потом удивляется, почему его API нихуя не работает как надо.

Смотри, представь себе, ты в баре. Ты подходишь к бармену и говоришь: «Дай мне пива». Это GET. Ты можешь просить его хоть десять раз — ты получишь информацию о пиве (или само пиво), но новый бокал с каждым разом не появится. Состояние бара — нихуя не изменилось. Идемпотентно.

А теперь ты говоришь: «Слушай, на столе №5 поставь бутылку виски и две рюмки». Это PUT. Ты полностью заменяешь то, что было на столе №5. Ты можешь повторить эту команду ещё раз — на столе всё равно будет бутылка виски и две рюмки, а не четыре. Конечное состояние — одинаковое. Тоже идемпотентно.

Дальше — DELETE. Ты говоришь: «Убери со стола №5 эту бутылку». Её убрали. Ты, обдолбанный, через пять минут орёшь то же самое. Бармен говорит: «Бля, да её уже нет, че ты доёбываешься?». Состояние системы — бутылки нет — нихуя не изменилось. Идемпотентно.

А вот POST — это пиздец. Это когда ты говоришь: «Сделай мне ещё один такой же коктейль». Каждый раз, когда ты это говоришь, бармен нихуя делает новый коктейль, и счёт твой растёт, как сумасшедший. Состояние системы (твой счёт и количество коктейлей в мире) меняется каждый раз. Это нихуя не идемпотентно. Вот почему для POST нужны ключи идемпотентности — чтобы бармен, если ты повторил запрос из-за того, что просто не услышал ответ в шуме, не наделал тебе овердохуища одинаковых коктейлей.

С PATCH вообще цирк. Если ты говоришь: «На столе №5 поставь соль на место перца» — это может быть идемпотентно. Но если ты скажешь: «Долей в мой стакан ещё 50 мл», то каждый повтор запроса нихуя добавит тебе ещё. И стакан, блядь, потечёт. Поэтому PATCH по умолчанию считаем неидемпотентным, иначе сам себе злой буратино.

Зачем это всё, ёпта? Представь, твой клиент отправил запрос на удаление юзера, а соединение нихуя проёбано. Клиент не получил ответ 200 OK и думает: «Бля, не удалилось». Он шлёт запрос ещё раз. Если DELETE идемпотентный — нихуя не страшно. Юзер уже удалён, система в том же состоянии. А если бы это был POST на списание денег — вот тут бы началась настоящая хитрая жопа. Деньги спишутся дважды, и будет тебе, чувак, хиросима и нигерсраки в бухгалтерии.

Вот смотри на код, тут нихуя не трогаем:

// Идемпотентные методы — повторяй, не бойся (в разумных пределах, естественно)
app.put('/api/wallet/:id', (req, res) => {
  // Установит баланс в N рублей. Вызови 100 раз — баланс всё равно будет N.
  updateWallet(req.params.id, { balance: req.body.balance });
  res.sendStatus(200);
});

app.delete('/api/cart/:itemId', (req, res) => {
  // Удалит товар. Вызови ещё раз — товара уже нет, но состояние корзины не изменится.
  removeItemFromCart(req.params.itemId);
  res.sendStatus(200); // или 404, если уже удалён — состояние-то одинаковое!
});

// НЕ идемпотентный метод — опасность, блядь!
app.post('/api/orders', (req, res) => {
  // Каждый вызов создаст НОВЫЙ заказ. Клиент отправил запрос 3 раза из-за таймаута?
  // Получай три одинаковых заказа и **терпения ноль ебать**.
  const newOrder = createOrder(req.body);
  res.status(201).json(newOrder);
});

Так что запомни, чувак: идемпотентность — это про то, чтобы итоговое состояние сервера после кучи одинаковых запросов было таким же, как и после одного. Не путай это с кодом ответа. После первого удаления может прийти 200, а после второго — 404, но ресурса-то в итоге нихуя нет в обоих случаях. Вот и вся магия. Э бошка думай, когда методы выбираешь.

Видео-ответы