> Как обеспечить идемпотентность API (Go)

Уровень: senior · Роль: backend · Категория: Технические вопросы

Компании: sferaplatform.ru

Стек: Go

> Пример ответа

Идемпотентность API означает, что повторный вызов одного и того же запроса с одинаковыми параметрами не изменяет состояние системы после первого успешного выполнения. В Go это можно реализовать несколькими способами.

1. Ключ идемпотентности (Idempotency Key)
Клиент передаёт уникальный ключ (например, UUID) в заголовке Idempotency-Key. Сервер проверяет, обрабатывался ли уже такой ключ, и возвращает сохранённый ответ. Пример на Go с использованием Redis:

GO
func HandlePayment(w http.ResponseWriter, r *http.Request) {
key := r.Header.Get("Idempotency-Key")
if key == "" {
http.Error(w, "Missing idempotency key", http.StatusBadRequest)
return
}
// Проверяем, есть ли уже результат
cached, err := redisClient.Get(ctx, key).Result()
if err == nil {
w.Write([]byte(cached))
return
}
// Выполняем операцию (например, списание средств)
result := processPayment(r.Body)
// Сохраняем результат с TTL (например, 24 часа)
redisClient.Set(ctx, key, result, 24*time.Hour)
w.Write([]byte(result))
}

2. Оптимистичная блокировка (версионирование)
Используется для обновления ресурсов. Клиент передаёт текущую версию (например, ETag или поле version). Сервер проверяет, что версия совпадает, иначе возвращает 409 Conflict.

GO
type Resource struct {
ID int `json:"id"`
Data string `json:"data"`
Version int `json:"version"`
}
func UpdateResource(w http.ResponseWriter, r *http.Request) {
var req Resource
json.NewDecoder(r.Body).Decode(&req)
// Проверяем версию в БД
current := db.GetResource(req.ID)
if current.Version != req.Version {
http.Error(w, "Version conflict", http.StatusConflict)
return
}
// Обновляем с инкрементом версии
req.Version++
db.UpdateResource(req)
json.NewEncoder(w).Encode(req)
}

3. Условные запросы (If-None-Match / If-Match)
Для GET-запросов - кэширование через ETag. Для PUT/PATCH - проверка, что ресурс не изменился с момента последнего чтения.

4. Идемпотентные операции на уровне БД
Использование INSERT ... ON CONFLICT DO NOTHING (PostgreSQL) или UPSERT для гарантии, что повторная вставка не создаст дубликат.

Важно:

  • Ключи идемпотентности должны быть уникальными и ограничены по времени (TTL).

  • Для критичных операций (платежи) используйте распределённые блокировки (например, через Redis Redlock).

  • Всегда возвращайте одинаковый HTTP-статус и тело ответа для повторных запросов с тем же ключом.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью