Laravel: Как отложить задания и слушателей в транзакциях базы данных

Источник: «How to delay Laravel jobs and listeners within database transactions»
Если у вас есть задания и слушатели, запускаемые в транзакциях базы данных, это может привести к несогласованности данных при откате транзакции. Узнайте как правильно обращаться с ними.

Как Laravel разработчик, вы могли столкнуться с проблемами, связанными с заданиями и слушателями, запускаемыми в транзакциях базы данных. Эти проблемы могут привести к ModelNotFoundException, несоответствиям ваших данных и другим проблемам, которые могут повлиять на надёжность вашего программного обеспечения. В этой статье мы рассмотрим, почему важны транзакции базы данных, общие проблемы, которые могут возникнуть при их использовании, и как правильно обрабатывать задания и слушателей в транзакциях.

Зачем использовать транзакции базы данных

Транзакции базы данных позволяют сгруппировать несколько операций базы данных в одну атомарную единицу. Если какая-либо часть транзакции завершается сбоем, все изменения откладываются, гарантируя, что ваша база данных останется в согласованном состоянии. В Laravel вы можете использовать транзакции для выполнения нескольких запросов к базе данных в рамках одной транзакции, используя метод DB::transaction. Например:

use Illuminate\Support\DB;

DB::transaction(function () {
// Выполняйте запросы к базе данных здесь
});

В качестве альтернативы, чтобы иметь больший контроль, вы также можете использовать метод без обратного вызова для управления транзакциями базы данных:

use Illuminate\Support\Facades\DB;

DB::beginTransaction();

try {
// Выполняйте запросы к базе данных здесь
DB::commit();
} catch (\Exception $e) {
DB::rollback();
// Здесь обрабатывайте исключения
}

Но ради этой статьи мы будем придерживаться первого варианта — использование обратных вызовов.

Распространённые проблемы с транзакциями базы данных

Хотя транзакции базы данных имеют решающее значение для поддержания согласованности данных в Laravel, есть некоторые распространённые проблемы, с которыми разработчики могут столкнуться при их использовании, особенно с заданиями и слушателями. В этом разделе мы обсудим эти вопросы более подробно.

ModelNotFoundException в заданиях в очереди

Запуск заданий с моделями, которые никогда не сохранялись из-за откатов, также известных как ModelNotFoundException в заданиях.

Рассмотрим сценарий, в котором вы используете задания для создания нового пользователя и добавления некоторых записей в базу данных. Однако если транзакция откатывается из-за ошибки, задание может быть отправлено с неполными или несуществующими данными. Это может привести к возникновению исключения ModelNotFoundException, поскольку задание попытается получить доступ к модели, которая никогда не сохранялась. Это может быть особенно проблематично, ели задание отвечает за отправку важных уведомлений или выполнение других важных действий.

Слушатели событий выполняющие действия, которые невозможно отменить

Предположим, у вас есть слушатель, выполняющий вызов внешнего API для синхронизации данных со сторонней службой. Если транзакция откатывается, это действие нельзя отменить, что приводит к несогласованности данных между вашим приложением и внешней службой. Это может привести к серьёзным проблемам, особенно если внешняя служба критически важна для бизнеса.

Решения

В Laravel есть отличные и простые решения для обеих проблем, гарантирующие, что задания или слушатели не будут выполняться до тех пор, пока транзакция базы данных не будет зафиксирована.

Давайте посмотрим!

Отправка задания Laravel после фиксации/коммита транзакции

Чтобы гарантировать, что задания отправляются только после фиксации/коммита транзакции, вы можете использовать метод afterCommit. Этот метод отправит задание только после того, как транзакция будет успешно зафиксирована. Пример:

DB::transaction(function () use ($data) {
// Выполняйте запросы к базе данных здесь

dispatch(new MyJob($data))->afterCommit();

// в качестве альтернативы, если задание использует трейт Dispatchable:
// MyJob::dispatch($data)->afterCommit();

// Выполните другие операции, которые потенциально могут привести к сбою,
// и откатите транзакцию
});

В качестве альтернативы, для большего контроля, вы также можете использовать метод DB::afterCommit() для обеспечения обратного вызова выполняющегося после фиксации/коммита транзакции:

DB::transaction(function () use ($data) {
// Выполняйте запросы к базе данных здесь

DB::afterCommit(function () {
dispatch(new MyJob($data));
});

// Выполните другие операции, которые потенциально могут привести к сбою,
// и откатите транзакцию
});

Задержка запуска слушателя событий Laravel после фиксации/коммита транзакции

Чтобы гарантировать, что слушатели выполняются только после того, как транзакция была зафиксирована, вы можете установить для вашего слушателя свойство afterCommit. Этот метод задерживает выполнение слушателя до тех пор, пока транзакция не будет зафиксирована. Пример:

class SendNotificationListener
{
public $afterCommit = true;

public function handle(MyEvent $event)
{
// Отправляйте уведомление по электронной почте здесь
}
}

Неважно, является ли слушатель синхронным или асинхронным (поставленным в очередь, реализующим интерфейс ShouldQueue) — Laravel выполнит это событие после того, как транзакция базы данных будет зафиксирована.

Очень удобно!


Таким образом, обработка заданий и слушателей базы данных требует тщательного рассмотрения, чтобы гарантировать согласованность данных вашего приложения. Используя метод afterCommit для отправки заданий позже и свойство $afterCommit = true для слушателей, чтобы отложить их до момента фиксации/коммита транзакции, можно избежать распространённых проблем, таких, как исключение ModelNotFoundException и вызовов внешнего API, которые нельзя откатить. Используя эти методы, вы можете гарантировать, что ваше Laravel приложение работает надёжно и последовательно.

Дополнительные материалы

Дополнительные материалы

Предыдущая Статья

Новое в Symfony 6.3 — Полезная нагрузка запроса

Следующая Статья

Новое в Symfony 6.3 — Команда отладки сериализатора