11..index ::
2- single: Messenger; Record messages
2+ single: Messenger; Record messages; Transaction messages
33
4- Events Recorder : Handle Events After CommandHandler Is Done
5- ===========================================================
4+ Transactional Messages : Handle Events After CommandHandler Is Done
5+ ==================================================================
66
7- Let's take the example of an application that has a command (a CQRS message) named
8- `` CreateUser ``. That command is handled by the `` CreateUserHandler `` which creates
9- a `` User `` object, stores thatobject to a database and dispatches a `` UserCreated `` event.
10- That event is also a normal message but is handled by an * event * bus.
7+ A message handler can `` dispatch `` new messages during execution, to either the same or
8+ a different bus (if the application has ` multiple buses < /messenger/multiple_buses >`_).
9+ Any errors or exceptions thatoccur during this process can have unintended consequences,
10+ such as:
1111
12- There are many subscribers to the ``UserCreated `` event, one subscriber may send
12+ - If using the ``DoctrineTransactionMiddleware `` and a dispatched message to the same bus
13+ and an exception is thrown, then any database transactions in the original handler will
14+ be rolled back.
15+ - If the message is dispatched to a different bus, then the dispatched message can still
16+ be handled even if the original handler encounters an exception.
17+
18+ An Example ``SignUpUser `` Process
19+ ---------------------------------
20+
21+ Let's take the example of an application with both a *command * and an *event * bus. The application
22+ dispatches a command named ``SignUpUser `` to the command bus. The command is handled by the
23+ ``SignUpUserHandler `` which creates a ``User `` object, stores that object to a database and
24+ dispatches a ``UserSignedUp `` event to the event bus.
25+
26+ There are many subscribers to the ``UserSignedUp `` event, one subscriber may send
1327a welcome email to the new user. We are using the ``DoctrineTransactionMiddleware ``
1428to wrap all database queries in one database transaction.
1529
16- **Problem: ** If an exception is thrown when sending the welcome email, then the user
30+ **Problem 1 : ** If an exception is thrown when sending the welcome email, then the user
1731will not be created because the ``DoctrineTransactionMiddleware `` will rollback the
1832Doctrine transaction, in which the user has been created.
1933
20- **Solution: ** The solution is to not dispatch the ``UserCreated `` event in the
21- ``CreateUserHandler `` but to just "record" the events. The recorded events will
22- be dispatched after ``DoctrineTransactionMiddleware `` has committed the transaction.
34+ **Problem 2: ** If an exception is thrown when saving the user to the database, the welcome
35+ email is still sent.
36+
37+ ``HandleMessageInNewTransaction `` Middleware
38+ --------------------------------------------
39+
40+ For many applications, the desired behavior is to have any messages dispatched by the handler
41+ to `only ` be handled after the handler finishes. This can be by using the
42+ ``HandleMessageInNewTransaction `` middleware and adding a ``Transaction `` stamp to
43+ `the message Envelope </components/messenger#adding-metadata-to-messages-envelopes >`_.
44+ This middleware enables us to add messages to a separate transaction that will only be
45+ dispatched *after * the current message handler finishes.
2346
24- To enable this, you simply just add the ``messenger.middleware.handles_recorded_messages ``
47+ Referencing the above example, this means that the ``UserSignedUp `` event would not be handled
48+ until *after * the ``SignUpUserHandler `` had completed and the new ``User `` was persisted to the
49+ database. If the ``SignUpUserHandler `` encounters an exception, the ``UserSignedUp `` event will
50+ never be handled and if an exception is thrown while sending the welcome email, the Doctrine
51+ transaction will not be rolled back.
52+
53+ To enable this, you need to add the ``handle_message_in_new_transaction ``
2554middleware. Make sure it is registered before ``DoctrineTransactionMiddleware ``
2655in the middleware chain.
2756
57+ **Note: ** The ``handle_message_in_new_transaction `` middleware must be loaded for *all * of the
58+ buses. For the example, the middleware must be loaded for both the command and event buses.
59+
2860..configuration-block ::
2961
3062 ..code-block ::yaml
@@ -33,45 +65,56 @@ in the middleware chain.
3365framework :
3466messenger :
3567default_bus :messenger.bus.command
68+
3669buses :
3770messenger.bus.command :
3871middleware :
39- -messenger.middleware.validation
40- -messenger.middleware.handles_recorded_messages :['@messenger.bus.event']
41- # Doctrine transaction must be after handles_recorded_messages middleware
42- -app.doctrine_transaction_middleware :['default']
72+ -validation
73+ -handle_message_in_new_transaction
74+ -doctrine_transaction
4375messenger.bus.event :
76+ default_middleware :allow_no_handlers
4477middleware :
45- -messenger.middleware.allow_no_handler
46- -messenger.middleware.validation
78+ -validation
79+ -handle_message_in_new_transaction
80+ -doctrine_transaction
81+
4782
4883 ..code-block ::php
4984
5085 namespace App\Messenger\CommandHandler;
5186
5287 use App\Entity\User;
53- use App\Messenger\Command\CreateUser ;
54- use App\Messenger\Event\UserCreatedEvent ;
88+ use App\Messenger\Command\SignUpUser ;
89+ use App\Messenger\Event\UserSignedUp ;
5590 use Doctrine\ORM\EntityManagerInterface;
56- use Symfony\Component\Messenger\MessageRecorderInterface;
91+ use Symfony\Component\Messenger\Envelope;
92+ use Symfony\Component\Messenger\Stamp\Transaction;
93+ use Symfony\Component\Messenger\MessageBusInterface;
5794
58- classCreateUserHandler
95+ classSignUpUserHandler
5996 {
6097 private $em;
61- private $eventRecorder ;
98+ private $eventBus ;
6299
63- public function __construct(MessageRecorderInterface $eventRecorder , EntityManagerInterface $em)
100+ public function __construct(MessageBusInterface $eventBus , EntityManagerInterface $em)
64101 {
65- $this->eventRecorder = $eventRecorder ;
102+ $this->eventBus = $eventBus ;
66103 $this->em = $em;
67104 }
68105
69- public function __invoke(CreateUser $command)
106+ public function __invoke(SignUpUser $command)
70107 {
71108 $user = new User($command->getUuid(), $command->getName(), $command->getEmail());
72109 $this->em->persist($user);
73110
74- // "Record" this event to be processed later by "handles_recorded_messages".
75- $this->eventRecorder->record(new UserCreatedEvent($command->getUuid());
111+ // The Transaction stamp marks the event message to be handled
112+ // only if this handler does not throw an exception.
113+
114+ $event = new UserSignedUp($command->getUuid());
115+ $this->eventBus->dispatch(
116+ (new Envelope($event))
117+ ->with(new Transaction())
118+ );
76119 }
77120 }