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

Як зробити споживача повідомлень ідемпотентним?

За доставки «щонайменше один раз» те саме повідомлення може прийти кілька разів. Ідемпотентний споживач гарантує, що повторна обробка не має додаткового ефекту.

Підхід 1 - журнал оброблених повідомлень. Кожне повідомлення має унікальний ідентифікатор. Перед обробкою споживач записує його в таблицю з унікальним індексом - в тій самій транзакції, що й зміни даних:

public function handle(OrderPaid $message): void
{
    DB::transaction(function () use ($message) {
        $inserted = DB::table('processed_messages')->insertOrIgnore([
            'message_id' => $message->id,
            'consumer' => 'loyalty.points',
            'processed_at' => now(),
        ]);

        if ($inserted === 0) {
            return;   // уже оброблено
        }

        LoyaltyAccount::forCustomer($message->customerId)->addPoints($message->points);
    });
}

Транзакція гарантує: або повідомлення позначено і бали нараховано, або ні те, ні інше. Окремий запис «оброблено» після дії (не в транзакції) залишає вікно для дубліката.

Підхід 2 - ідемпотентна операція за природою. Замість «додати 100 балів» - «встановити бали за замовлення 42 у 100»:

LoyaltyEntry::updateOrCreate(
    ['order_id' => $message->orderId],
    ['points' => $message->points],
);

Повтор перезаписує те саме значення.

Підхід 3 - перевірка стану. «Якщо замовлення вже позначено оплаченим - нічого не робити». Працює, якщо стан однозначно показує, що дію виконано.

Підхід 4 - ключ ідемпотентності в зовнішньому виклику. Якщо споживач викликає платіжну систему, передати ключ (наприклад, id повідомлення) - провайдер сам відкине повтор.

Складні випадки:

  • побічні ефекти поза базою (лист, SMS, виклик API) не відкочуються транзакцією. Якщо лист надіслано, а позначка «оброблено» не збереглася, повтор надішле ще раз. Рішення - ключ ідемпотентності в провайдера чи окремий крок із власною позначкою;
  • порядок повідомлень: дублікат старого повідомлення може прийти після нового - перевіряти версію чи час сутності;
  • очищення журналу: таблиця оброблених повідомлень росте - старі записи видаляють після періоду, за який дублікати вже неможливі.

У Laravel для джоб у черзі: ShouldBeUnique запобігає дублікатам у черзі, але не замінює ідемпотентності обробника - джоба все одно може виконатися двічі після збою воркера.

Докладніше в документації: Microservices.io: Idempotent Consumer

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