Транзакционные сообщение: обрабатывайте сообщения после того, как обработка закончена
Дата обновления перевода 2024-07-25
Транзакционные сообщение: обрабатывайте сообщения после того, как обработка закончена
Обработчик сообщений может dispatch
новое сообщение во время обработки других, в тот
же либо другой автобус (если приложение имеет несколько автобусов).
Любые ошибки или исключения, которые возникают в этом процессе, могут иметь ненамеренные
последствия, вроде:
- Если вы используете
DoctrineTransactionMiddleware
а запущенное сообщение вызывает исключение, то любые транзакции базы данных в изначальном обработчике будут отменены. - Если сообщение запущено в другой автобус, то запущенное сообщение будет обработано, даже если какой-то код позже в текущем обработчике вызовет исключение.
Пример процесса RegisterUser
Давайте в качестве примера возьмем приложение с автобусами команд и событий.
Приложение запускает команду под названием RegisterUser
в автобус команд. Команда
обрабатывается RegisterUserHandler
, что создает объект User
, сохраняет этот
объект в базе данных и запускает сообщение UserRegistered
в автобус событий.
Существует много обработчиков сообщения UserRegistered
, один может отправлять
приветственное письмо новому пользователю. Мы используем DoctrineTransactionMiddleware
,
чтобы обернуть все запросы БД в одну транзакцию БД.
Проблема №1: Если во время отправки приветственного письма вызывается исключение,
то пользователь не будет создан, так как DoctrineTransactionMiddleware
откатится
к транзакции Doctrine, в которой был создан пользователь.
Проблема №2: Если исключение вызывается при сохранении пользователя в БД, приветственное письмо все равно будет отправлено, так как оно обрабатывается асинхронно.
Промежуточное ПО DispatchAfterCurrentBusMiddleware
Для многих приложений, желаемое поведение - обрабатывать только сообщения, которые
запускаются обработчиком после того, как обработчик полностью закончил работу. Это можно
сделать используя DispatchAfterCurrentBusMiddleware
и добавив штамп
DispatchAfterCurrentBusStamp
к конверту сообщения :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
// src/Messenger/CommandHandler/RegisterUserHandler.php
namespace App\Messenger\CommandHandler;
use App\Entity\User;
use App\Messenger\Command\RegisterUser;
use App\Messenger\Event\UserRegistered;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Messenger\Stamp\DispatchAfterCurrentBusStamp;
class RegisterUserHandler
{
public function __construct(
private MessageBusInterface $eventBus,
private EntityManagerInterface $em,
) {
}
public function __invoke(RegisterUser $command): void
{
$user = new User($command->getUuid(), $command->getName(), $command->getEmail());
$this->em->persist($user);
// DispatchAfterCurrentBusStamp помечает сообщение события для обработки
// только если этот обработчик не вызывает исключения.
$event = new UserRegistered($command->getUuid());
$this->eventBus->dispatch(
(new Envelope($event))
->with(new DispatchAfterCurrentBusStamp())
);
// ...
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
// src/Messenger/EventSubscriber/WhenUserRegisteredThenSendWelcomeEmail.php
namespace App\Messenger\EventSubscriber;
use App\Entity\User;
use App\Messenger\Event\UserRegistered;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\RawMessage;
class WhenUserRegisteredThenSendWelcomeEmail
{
public function __construct(
private MailerInterface $mailer,
EntityManagerInterface $em,
) {
}
public function __invoke(UserRegistered $event): void
{
$user = $this->em->getRepository(User::class)->find($event->getUuid());
$this->mailer->send(new RawMessage('Welcome '.$user->getFirstName()));
}
}
Это означает, что сообщение UserRegistered
не будет обработано до тех пор,
пока не будет выполнен RegisterUserHandler
и новый User
не будет сохранен
в базу данных. Если RegisterUserHandler
столкнется с исключением, событие
UserRegistered
никогда не будет обработано. А если исключение будет вызывано
во время отправки приветственного письма, транзакция Doctrine не будет отменена.
Note
Если WhenUserRegisteredThenSendWelcomeEmail
вызывает исключение, оно будет
обернуто в DelayedMessageHandlingException
. Использование
DelayedMessageHandlingException::getExceptions
предоставит вам все исключения,
которые вызываются во время обработки сообщения с
DispatchAfterCurrentBusStamp
.
Промежуточное ПО dispatch_after_current_bus
подключается по умолчанию. Если вы
конфигурируете свое промежуточное ПО вручную, не забудьте зарегистрировать
dispatch_after_current_bus
перед doctrine_transaction
в цепочке промежуточного
ПО. Также, промежуточное ПО dispatch_after_current_bus
должно быть загружено для
всех используемых автобусов.