Laravel: Как отложить задания и слушателей в транзакциях базы данных
Как 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 приложение работает надёжно и последовательно.
Дополнительные материалы
- Laravel: Использование транзакций
- Laravel: Как работают транзакции базы данных
- Laravel: Как обрабатывать длительные задания