← Назад к вопросам

Почему PATCH является идемпотентным?

2.0 Middle🔥 151 комментариев
#API и интеграции

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Почему PATCH является идемпотентным

Стоп. Тут важное уточнение: PATCH НЕ всегда идемпотентен. Это частая ошибка в понимании. Давайте разберёмся правильно.

Определение идемпотентности

Идемпотентная операция — это операция, которая при многократном повторении с одними и теми же параметрами дает тот же результат:

f(x) = f(f(x)) = f(f(f(x))) = ...

Является ли PATCH идемпотентным?

Краткий ответ: ЭТО ЗАВИСИТ от того, как PATCH реализован.

Когда PATCH идемпотентен

Пример 1: Замена полей (идемпотентен)

PATCH /api/users/123
{"status": "active"}

Множественные вызовы:

  1. Первый вызов: user.status = "active" ✓
  2. Второй вызов: user.status = "active" (уже был active) ✓
  3. Третий вызов: user.status = "active" ✓

Результат: идентичен. Идемпотентен.

Пример 2: Установка флага (идемпотентен)

PATCH /api/users/123
{"verified": true}

Множественные вызовы — всегда одинаковый результат. Идемпотентен.

Когда PATCH НЕ идемпотентен

Пример 1: Инкремент (НЕ идемпотентен)

PATCH /api/accounts/123
{"balance": "+100"}  // Увеличить на 100

Множественные вызовы:

  1. Первый вызов: balance 100 → 200
  2. Второй вызов: balance 200 → 300
  3. Третий вызов: balance 300 → 400

Результат разный. НЕ идемпотентен.

Пример 2: Append (добавить к массиву)

PATCH /api/comments/123
{"tags": ["add", "new-tag"]}

Множественные вызовы:

  1. Первый: tags = ["old", "new-tag"]
  2. Второй: tags = ["old", "new-tag", "new-tag"]
  3. Третий: tags = ["old", "new-tag", "new-tag", "new-tag"]

Дубликаты. НЕ идемпотентен.

Пример 3: Удаление с индексом

PATCH /api/users/123
{"removeField": "phone"}  // Удалить поле

Множественные вызовы:

  1. Первый: phone удалён ✓
  2. Второй: phone уже удалён, попытка удаления несуществующего... баг?

Может быть ошибка или молчаливое проигнорирование. НЕ идемпотентен по определению.

RFC 5789 (стандарт PATCH)

Официальный стандарт НЕ требует идемпотентности:

"PATCH может быть не идемпотентным в отличие от PUT."

Это ключевая разница:

ОперацияИдемпотентнаОписание
GETДаЧтение не меняет состояние
PUTДаПолная замена (F(x) = F(F(x)))
DELETEДаУдаление несуществующего = удаление существующего
PATCHЗависитМожет быть, а может не быть
POSTНетСоздание нового каждый раз

Почему возникла ошибка в понимании

Возможно, перепутали с PUT. PUT идемпотентен всегда, потому что это полная замена:

PUT /api/users/123
{"name": "Alice", "age": 30}

Множественные вызовы:

  1. Первый: {"name": "Alice", "age": 30}
  2. Второй: {"name": "Alice", "age": 30} (замена на то же самое)
  3. Третий: {"name": "Alice", "age": 30}

ПУТ всегда идемпотентен, потому что ты заменяешь весь ресурс на конкретное значение.

PATCH vs PUT: правильное использование

PUT — идемпотентен

PUT /api/users/123
{"name": "Alice", "age": 30}  // Полная замена

PATCH — может быть идемпотентен или нет

// Идемпотентен: замена поля
PATCH /api/users/123
{"name": "Alice"}

// НЕ идемпотентен: инкремент
PATCH /api/users/123
{"score": "+10"}

// НЕ идемпотентен: добавление к массиву
PATCH /api/users/123
{"tags": {"add": "new"}}

Практическая рекомендация

Как реализовать PATCH правильно:

  1. Если нужна идемпотентность:

    • Замещай поля полностью
    • Избегай операций вроде "+", "append", "remove by index"
    • Используй JSON Patch (RFC 6902) с absolute values
  2. Если нужны операции типа inrement:

    • Документируй, что операция НЕ идемпотентна
    • Используй optimistic locking (проверяй версию)
    • Или лучше используй отдельный endpoint: POST /api/users/123/score/increment

Правильный PATCH:

PATCH /api/users/123
[
  {"op": "replace", "path": "/name", "value": "Alice"},
  {"op": "replace", "path": "/status", "value": "active"}
]

Это идемпотентен — множественные вызовы дадут одинаковый результат.

Вывод

  • PATCH может быть идемпотентным, но не обязан
  • PUT всегда идемпотентен
  • Стандарт (RFC 5789) явно разрешает PATCH быть не идемпотентным
  • Если нужна идемпотентность, используй замену полей (replace), а не операции (increment, append, delete by index)