Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Як захиститися від втрачених оновлень через If-Match і 412 Precondition Failed?

Проблема втраченого оновлення. Два менеджери відкрили одне замовлення. Перший змінив адресу доставки й зберіг. Другий, дивлячись на стару версію, змінив коментар і зберіг - і перезаписав адресу першого старим значенням. Ніхто не отримав помилки.

Оптимістичне блокування через умовні запити:

1. Сервер віддає версію ресурсу в ETag:

GET /api/orders/42

HTTP/1.1 200 OK
ETag: "v17"

2. Клієнт оновлює з умовою «лише якщо версія досі та сама»:

PATCH /api/orders/42
If-Match: "v17"

{"comment": "Подзвонити перед доставкою"}

3. Сервер перевіряє:

  • версія збігається - оновлює, повертає новий ETag: "v18";
  • версія змінилася - 412 Precondition Failed, нічого не змінює. Клієнт перечитує ресурс, показує користувачу зміни й пропонує злити їх.

Якщо клієнт не надіслав If-Match для ресурсу, де конфлікти критичні, сервер може вимагати його: 428 Precondition Required (RFC 6585).

Реалізація в Laravel:

public function update(UpdateOrderRequest $request, Order $order): JsonResponse
{
    $expected = trim($request->header('If-Match', ''), '"');

    $updated = Order::whereKey($order->id)
        ->where('version', (int) ltrim($expected, 'v'))
        ->update([...$request->validated(), 'version' => DB::raw('version + 1')]);

    abort_if($updated === 0, 412, 'Ресурс змінено іншим користувачем');

    return OrderResource::make($order->fresh())
        ->response()
        ->header('ETag', '"v'.$order->fresh()->version.'"');
}

Ключове - перевірка й оновлення одним атомарним UPDATE ... WHERE version = ?. Варіант «прочитати версію, порівняти в PHP, потім записати» має ту саму гонитву, від якої ми захищаємося.

Джерело версії: окрема колонка version (лічильник), updated_at з мікросекундами або хеш вмісту. Лічильник найнадійніший: updated_at може збігтися при двох змінах в одну секунду.

Чим це краще за песимістичне блокування (заблокувати запис на час редагування): не потрібно тримати блокування між HTTP-запитами, немає «завислих» блокувань, коли користувач закрив вкладку. Конфлікти рідкісні - і обробляються лише тоді, коли справді стаються.

Слабкі ETag (W/"v17") для If-Match не підходять - порівняння має бути сильним.

Докладніше в документації: Заголовок If-Match

Перевір себе

20 випадкових питань за спробу, після завершення - розбір кожної помилки

Схожі питання