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 5e1033a

Browse filesBrowse files
committed
feature #10015 [Messenger] Added documentation about DispatchAfterCurrentBusMiddleware (Nyholm, gubler, OskarStark)
This PR was submitted for the master branch but it was merged into the 4.3 branch instead (closes #10015). Discussion ---------- [Messenger] Added documentation about DispatchAfterCurrentBusMiddleware Documentation to PR: symfony/symfony#27844 ![Screenshot 2019-03-17 at 11 43 23](https://user-images.githubusercontent.com/1275206/54489171-edee4880-48a9-11e9-8028-ac501e62dfcb.png) ![Screenshot 2019-03-17 at 11 43 34](https://user-images.githubusercontent.com/1275206/54489173-edee4880-48a9-11e9-877f-f749f242b502.png) ![Screenshot 2019-03-17 at 11 43 39](https://user-images.githubusercontent.com/1275206/54489172-edee4880-48a9-11e9-84f1-d78e68474c7a.png) Commits ------- 0d12880 minor updates according to feedback ec3e1d7 updates according to feedback 3e99663 Update messenger/message-recorder.rst fe81abc Update messenger/message-recorder.rst c1cef9e Update messenger/message-recorder.rst cfa56f7 Update messenger/message-recorder.rst 06ec8a4 Added PHP and XML config 797f530 Minor updates 30e958c Explain HandleMessageInNewTransaction middleware 84b81e3 Added fixes ca22c3c a user fff8659 According to feedback afdddbf Added documentation about message recorder
2 parents cde5dde + 0d12880 commit 5e1033a
Copy full SHA for 5e1033a

File tree

1 file changed

+192
-0
lines changed
Filter options

1 file changed

+192
-0
lines changed

‎messenger/message-recorder.rst

Copy file name to clipboard
+192Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
.. index::
2+
single: Messenger; Record messages; Transaction messages
3+
4+
Transactional Messages: Handle Events After CommandHandler is Done
5+
==================================================================
6+
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:
11+
12+
- If using the ``DoctrineTransactionMiddleware`` and a dispatched message throws an exception,
13+
then any database transactions in the original handler will be rolled back.
14+
- If the message is dispatched to a different bus, then dispatched message will be
15+
handled even if the current handler throws an exception.
16+
17+
An Example ``RegisterUser`` Process
18+
-----------------------------------
19+
20+
Let's take the example of an application with both a *command* and an *event* bus. The application
21+
dispatches a command named ``RegisterUser`` to the command bus. The command is handled by the
22+
``RegisterUserHandler`` which creates a ``User`` object, stores that object to a database and
23+
dispatches a ``UserRegistered`` event to the event bus.
24+
25+
There are many subscribers to the ``UserRegistered`` event, one subscriber may send
26+
a welcome email to the new user. We are using the ``DoctrineTransactionMiddleware``
27+
to wrap all database queries in one database transaction.
28+
29+
**Problem 1:** If an exception is thrown when sending the welcome email, then the user
30+
will not be created because the ``DoctrineTransactionMiddleware`` will rollback the
31+
Doctrine transaction, in which the user has been created.
32+
33+
**Problem 2:** If an exception is thrown when saving the user to the database, the welcome
34+
email is still sent because it is handled asynchronously.
35+
36+
``DispatchAfterCurrentBusMiddleware`` Middleware
37+
------------------------------------------------
38+
39+
For many applications, the desired behavior is to have any messages dispatched by the handler
40+
to `only` be handled after the handler finishes. This can be by using the
41+
``DispatchAfterCurrentBusMiddleware`` middleware and adding a ``DispatchAfterCurrentBusStamp``
42+
stamp to `the message Envelope </components/messenger#adding-metadata-to-messages-envelopes>`_.
43+
44+
Referencing the above example, this means that the ``UserRegistered`` event would not be handled
45+
until *after* the ``RegisterUserHandler`` had completed and the new ``User`` was persisted to the
46+
database. If the ``RegisterUserHandler`` encounters an exception, the ``UserRegistered`` event will
47+
never be handled and if an exception is thrown while sending the welcome email, the Doctrine
48+
transaction will not be rolled back.
49+
50+
The ``dispatch_after_current_bus`` middleware is enabled by default. It is configured as the
51+
first middleware on all busses. When doing a highly custom or special configuration, then make
52+
sure ``dispatch_after_current_bus`` is registered before ``doctrine_transaction``
53+
in the middleware chain.
54+
55+
**Note:** The ``dispatch_after_current_bus`` middleware must be loaded for *all* of the
56+
buses. For the example, the middleware must be loaded for both the command and event bus.
57+
58+
.. configuration-block::
59+
60+
.. code-block:: yaml
61+
62+
# config/packages/messenger.yaml
63+
framework:
64+
messenger:
65+
default_bus: messenger.bus.command
66+
67+
buses:
68+
messenger.bus.command:
69+
middleware:
70+
- validation
71+
messenger.bus.event:
72+
default_middleware: allow_no_handlers
73+
middleware:
74+
- validation
75+
76+
.. code-block:: xml
77+
78+
<!-- config/packages/messenger.xml -->
79+
<?xml version="1.0" encoding="UTF-8" ?>
80+
<container xmlns="http://symfony.com/schema/dic/services"
81+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
82+
xmlns:framework="http://symfony.com/schema/dic/symfony"
83+
xsi:schemaLocation="http://symfony.com/schema/dic/services
84+
https://symfony.com/schema/dic/services/services-1.0.xsd">
85+
86+
<framework:config>
87+
<framework:messenger default_bus="messenger.bus.command">
88+
<framework:bus name="messenger.bus.command">
89+
<framework:middleware id="validation">
90+
<framework:middleware id="doctrine_transaction">
91+
</framework:bus>
92+
<framework:bus name="messenger.bus.command" default_middleware="allow_no_handlers">
93+
<framework:middleware id="validation">
94+
<framework:middleware id="doctrine_transaction">
95+
</framework:bus>
96+
</framework:messenger>
97+
</framework:config>
98+
</container>
99+
100+
.. code-block:: php
101+
102+
// config/packages/messenger.php
103+
$container->loadFromExtension('framework', [
104+
'messenger' => [
105+
'default_bus' => 'messenger.bus.command',
106+
'buses' => [
107+
'messenger.bus.command' => [
108+
'middleware' => ['validation', 'doctrine_transaction'],
109+
],
110+
'messenger.bus.event' => [
111+
'default_middleware' => 'allow_no_handlers',
112+
'middleware' => ['validation', 'doctrine_transaction'],
113+
],
114+
],
115+
],
116+
]);
117+
118+
.. code-block:: php
119+
120+
namespace App\Messenger\CommandHandler;
121+
122+
use App\Entity\User;
123+
use App\Messenger\Command\RegisterUser;
124+
use App\Messenger\Event\UserRegistered;
125+
use Doctrine\ORM\EntityManagerInterface;
126+
use Symfony\Component\Messenger\Envelope;
127+
use Symfony\Component\Messenger\Stamp\DispatchAfterCurrentBusStamp;
128+
use Symfony\Component\Messenger\MessageBusInterface;
129+
130+
class RegisterUserHandler
131+
{
132+
private $eventBus;
133+
private $em;
134+
135+
public function __construct(MessageBusInterface $eventBus, EntityManagerInterface $em)
136+
{
137+
$this->eventBus = $eventBus;
138+
$this->em = $em;
139+
}
140+
141+
public function __invoke(RegisterUser $command)
142+
{
143+
$user = new User($command->getUuid(), $command->getName(), $command->getEmail());
144+
$this->em->persist($user);
145+
146+
// The DispatchAfterCurrentBusStamp marks the event message to be handled
147+
// only if this handler does not throw an exception.
148+
149+
$event = new UserRegistered($command->getUuid());
150+
$this->eventBus->dispatch(
151+
(new Envelope($event))
152+
->with(new DispatchAfterCurrentBusStamp())
153+
);
154+
155+
// ...
156+
}
157+
}
158+
159+
.. code-block:: php
160+
161+
namespace App\Messenger\EventSubscriber;
162+
163+
use App\Entity\User;
164+
use App\Messenger\Event\UserRegistered;
165+
use Doctrine\ORM\EntityManagerInterface;
166+
use Symfony\Component\Mailer\MailerInterface;
167+
use Symfony\Component\Mime\RawMessage;
168+
169+
class WhenUserRegisteredThenSendWelcomeEmail
170+
{
171+
private $mailer;
172+
private $em;
173+
174+
public function __construct(MailerInterface $mailer, EntityManagerInterface $em)
175+
{
176+
$this->mailer = $mailer;
177+
$this->em = $em;
178+
}
179+
180+
public function __invoke(UserRegistered $event)
181+
{
182+
$user = $this->em->getRepository(User::class)->find(new User($event->getUuid()));
183+
184+
$this->mailer->send(new RawMessage('Welcome '.$user->getFirstName()));
185+
}
186+
}
187+
188+
.. note::
189+
190+
If ``WhenUserRegisteredThenSendWelcomeEmail`` throws an exception, that exception
191+
will be wrapped into a ``DelayedMessageHandlingException``. Using ``DelayedMessageHandlingException::getExceptions``
192+
will give you all exceptions that are thrown while handing a message with the ``DispatchAfterCurrentBusStamp``.

0 commit comments

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