> Как обеспечить идемпотентность API (Go)
Уровень: senior · Роль: backend · Категория: Технические вопросы
Компании: sferaplatform.ru
Стек: Go
> Пример ответа
Идемпотентность API означает, что повторный вызов одного и того же запроса с одинаковыми параметрами не изменяет состояние системы после первого успешного выполнения. В Go это можно реализовать несколькими способами.
1. Ключ идемпотентности (Idempotency Key)
Клиент передаёт уникальный ключ (например, UUID) в заголовке Idempotency-Key. Сервер проверяет, обрабатывался ли уже такой ключ, и возвращает сохранённый ответ. Пример на Go с использованием Redis:
GOfunc 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.
GOtype Resource struct {ID int `json:"id"`Data string `json:"data"`Version int `json:"version"`}func UpdateResource(w http.ResponseWriter, r *http.Request) {var req Resourcejson.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-статус и тело ответа для повторных запросов с тем же ключом.
> Похожие задачи по backend
Как работает использование нескольких CTE с JOIN в SQL: по индексам или в памяти?
Зачем нужен номер строки (row number) в SQL запросах
Что делать если EXPLAIN показывает sequential scan вместо индексного сканирования
Что будет если не использовать указатель при возврате структуры в Go
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью