Что такое идемпотентность операций?
Идемпотентность – свойство операции, при котором многократное выполнение с одними и теми же входными данными даёт тот же результат, что и однократное выполнение. Как нажатие кнопки “вызвать лифт” – сколько ни нажимай, лифт приедет один раз.
Пример
Идемпотентная операция:
f(x) = f(f(x))
Перевести статус заказа в "ОПЛАЧЕН" -- можно вызвать 100 раз, результат один.
GET /api/accounts/123 -- сколько ни вызывай, данные не изменятся.
НЕ идемпотентная операция:
"Пополнить счёт на 1000 руб." -- при повторном вызове сумма удвоится!
HTTP-методы и идемпотентность
| Метод | Идемпотентный | Безопасный |
|---|---|---|
| GET | Да | Да |
| HEAD | Да | Да |
| PUT | Да | Нет |
| DELETE | Да | Нет |
| POST | Нет | Нет |
| PATCH | Нет | Нет |
Почему это критично
- Сетевые сбои – клиент не получил ответ и повторяет запрос.
- Retry-политики в микросервисах – повторные попытки при таймауте.
- Дублирование сообщений в очередях (at-least-once delivery в Kafka).
- В финансовых системах повторный вызов может привести к двойному списанию или зачислению.
Способы обеспечения идемпотентности
1. Idempotency Key
Клиент передаёт уникальный ключ. Сервер проверяет, обрабатывалась ли операция с таким ключом, и при повторном запросе возвращает сохранённый результат.
Пример кода
@PostMapping("/payments")
public ResponseEntity<PaymentResult> createPayment(
@RequestHeader("Idempotency-Key") String idempotencyKey,
@RequestBody CreatePaymentRequest request) {
// Проверяем, не обрабатывали ли мы уже этот запрос
Optional<PaymentResult> existing = idempotencyStore.find(idempotencyKey);
if (existing.isPresent()) {
return ResponseEntity.ok(existing.get()); // возвращаем прежний результат
}
PaymentResult result = paymentService.create(request);
idempotencyStore.save(idempotencyKey, result);
return ResponseEntity.status(HttpStatus.CREATED).body(result);
}
2. Условные операции
Использование версий, ETag или условных обновлений в БД:
Пример
-- Идемпотентное обновление: статус изменится только один раз
UPDATE payments SET status = 'COMPLETED'
WHERE id = :id AND status = 'PENDING';
3. Естественная идемпотентность
Проектирование операций как установка конечного состояния, а не как приращение:
Пример
// Не идемпотентно:
account.addBalance(1000); // при повторе удвоится
// Идемпотентно:
account.setBalance(5000); // при повторе результат тот же
Итог
При проектировании API и обработчиков событий всегда задавайте вопрос: “Что произойдёт, если эта операция выполнится дважды?” Если ответ “ничего страшного” – операция идемпотентна. Если нет – нужен один из описанных механизмов защиты.
На собеседовании: Интервьюер хочет услышать конкретные способы обеспечения идемпотентности (Idempotency Key, условные обновления), а не только определение. Частая ошибка – утверждать, что DELETE всегда идемпотентен, забывая о побочных эффектах (события, триггеры).