Skip to content

Commit 5e1033a

Browse 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

File tree

1 file changed

+192
-0
lines changed

1 file changed

+192
-0
lines changed

messenger/message-recorder.rst

Lines 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)