Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 30e958c

Browse filesBrowse files
gublerweaverryan
authored andcommitted
Explain HandleMessageInNewTransaction middleware
The current PR is for HandleMessageInNewTransaction middleware instead of the original RecordsMessages middleware. This documentation covers the new middleware.
1 parent 84b81e3 commit 30e958c
Copy full SHA for 30e958c

File tree

1 file changed

+72
-29
lines changed
Filter options

1 file changed

+72
-29
lines changed

‎messenger/message-recorder.rst

Copy file name to clipboard
+72-29Lines changed: 72 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,62 @@
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 that object 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 that occur 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
1327
a welcome email to the new user. We are using the ``DoctrineTransactionMiddleware``
1428
to 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
1731
will not be created because the ``DoctrineTransactionMiddleware`` will rollback the
1832
Doctrine 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``
2554
middleware. Make sure it is registered before ``DoctrineTransactionMiddleware``
2655
in 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.
3365
framework:
3466
messenger:
3567
default_bus: messenger.bus.command
68+
3669
buses:
3770
messenger.bus.command:
3871
middleware:
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
4375
messenger.bus.event:
76+
default_middleware: allow_no_handlers
4477
middleware:
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-
class CreateUserHandler
95+
class SignUpUserHandler
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
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.