Вендэлл Адриэль представил пакет Laravel Idempotency, который добавляет поддержку идемпотентности для write-запросов в Laravel. При повторной отправке POST, PUT или PATCH с тем же ключом и тем же payload пакет возвращает закэшированный ответ вместо повторного выполнения обработчика. Это используется в сценариях с платежами, созданием заказов и других API, где возможны повторы из-за сетевых сбоев.
Подключение middleware
Пакет можно применить к маршруту через стандартный middleware:
use WendellAdriel\Idempotency\Http\Middleware\Idempotent;
Route::post('/orders', StoreOrderController::class)
->middleware(Idempotent::class);Middleware ожидает заголовок Idempotency-Key. Если тот же ключ приходит повторно с идентичными данными, возвращается исходный ответ с дополнительным заголовком Idempotency-Replayed: true.
Настройки можно задать на уровне маршрута:
Route::post('/payments', ChargePaymentController::class)->middleware(
Idempotent::using(
ttl: 600,
lockTimeout: 30,
required: false,
scope: \WendellAdriel\Idempotency\Enums\IdempotencyScope::Ip,
header: 'X-Idempotency-Key',
)
);Также доступен вариант через PHP-атрибуты для класса или метода контроллера:
use WendellAdriel\Idempotency\Attributes\Idempotent;
use WendellAdriel\Idempotency\Enums\IdempotencyScope;
#[Idempotent]
class PaymentController
{
#[Idempotent(ttl: 600, lockTimeout: 30, scope: IdempotencyScope::Ip)]
public function store()
{
// ...
}
}Атрибут наследует поведение стандартных middleware Laravel, поэтому поддерживаются параметры only и except.
Область действия ключей
Ключи идемпотентности могут работать в разных областях, которые настраиваются глобально или для конкретного маршрута:
user- ключи разделяются по пользователям, для гостей используется IPip- разделение по IP-адресуglobal- один ключ действует для всех пользователей и IP
Обработка конфликтов
Пакет обрабатывает два типа конфликтов. Если приходит тот же ключ, но с другим payload, возвращается ошибка 422 Unprocessable Entity.
Если второй идентичный запрос поступает до завершения первого, возвращается 409 Conflict с заголовком Retry-After: 1.
Обе ситуации реализованы через атомарные блокировки кеша, поэтому требуется драйвер с поддержкой lock, например Redis или Memcached.
Artisan-команды
Пакет добавляет команды для работы с кешированными записями. Просмотр активных записей:
php artisan idempotency:list --scope=user --id=5Удаление записей:
# удалить все записи пользователя
php artisan idempotency:forget --scope=user --id=5 --force
# удалить записи по ключу
php artisan idempotency:forget --key=checkout-1 --forceКоманда удаления требует подтверждения, если не указан флаг --force.