Gymterview
middle

Что такое идемпотентный ключ (Idempotency Key)?

Идемпотентный ключ (Idempotency Key) — уникальный идентификатор, передаваемый клиентом в запросе, позволяющий серверу распознавать повторные запросы и не выполнять операцию дважды.

Аналогия из жизни: идемпотентный ключ — как номер платёжного поручения в банке. Если вы случайно отправите одно и то же поручение дважды, банк увидит, что поручение с таким номером уже обработано, и не спишет деньги повторно.

Зачем нужен

Метод POST не является идемпотентным. При сетевых ошибках (таймаут, разрыв соединения) клиент не знает, был ли запрос обработан. Повторная отправка может привести к дублированию (двойное списание денег, двойное создание заказа).

Как работает

Пример
POST /api/payments HTTP/1.1
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json

{"amount": 1000, "currency": "RUB", "recipientId": 42}
  1. Сервер проверяет, обрабатывался ли запрос с таким ключом.
  2. Если нет — обрабатывает и сохраняет результат.
  3. Если да — возвращает сохранённый результат без повторного выполнения.
Реализация в Spring
@RestController
@RequestMapping("/api/payments")
@RequiredArgsConstructor
public class PaymentController {

    private final PaymentService paymentService;
    private final IdempotencyService idempotencyService;

    @PostMapping
    public ResponseEntity<PaymentResponse> createPayment(
            @RequestHeader("Idempotency-Key") String idempotencyKey,
            @Valid @RequestBody PaymentRequest request) {

        // Проверяем, есть ли уже результат
        return idempotencyService.findByKey(idempotencyKey)
            .map(cached -> ResponseEntity.ok(cached))
            .orElseGet(() -> {
                PaymentResponse response = paymentService.processPayment(request);
                idempotencyService.save(idempotencyKey, response);
                return ResponseEntity.status(HttpStatus.CREATED).body(response);
            });
    }
}

@Service
@RequiredArgsConstructor
public class IdempotencyService {

    private final IdempotencyRepository repository;

    public Optional<PaymentResponse> findByKey(String key) {
        return repository.findByKey(key)
            .map(entry -> deserialize(entry.getResponse()));
    }

    public void save(String key, PaymentResponse response) {
        IdempotencyEntry entry = new IdempotencyEntry();
        entry.setKey(key);
        entry.setResponse(serialize(response));
        entry.setCreatedAt(LocalDateTime.now());
        entry.setExpiresAt(LocalDateTime.now().plusHours(24));
        repository.save(entry);
    }
}

Ключи обычно имеют срок жизни (24 часа) и хранятся в БД или Redis. Паттерн широко используется в платёжных системах (Stripe, Tinkoff, YooMoney).

На собеседовании: ключевое — объяснить, зачем нужен Idempotency Key (для POST-запросов при сетевых ошибках) и как он работает (проверка + сохранение результата). Частая ошибка — путать идемпотентность метода и Idempotency Key.