-
-
Notifications
You must be signed in to change notification settings - Fork 5.2k
[Messenger] Tweaks for DispatchAfterCurrentBusMiddleware entry #11597
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
.. index:: | ||
single: Messenger; Record messages; Transaction messages | ||
|
||
Transactional Messages: Handle Events After CommandHandler is Done | ||
Transactional Messages: Handle New Messages After Handling is Done | ||
================================================================== | ||
|
||
A message handler can ``dispatch`` new messages during execution, to either the same or | ||
|
@@ -11,18 +11,18 @@ such as: | |
|
||
- If using the ``DoctrineTransactionMiddleware`` and a dispatched message throws an exception, | ||
then any database transactions in the original handler will be rolled back. | ||
- If the message is dispatched to a different bus, then dispatched message will be | ||
handled even if the current handler throws an exception. | ||
- If the message is dispatched to a different bus, then the dispatched message will | ||
be handled even if some code later in the current handler throws an exception. | ||
|
||
An Example ``RegisterUser`` Process | ||
----------------------------------- | ||
|
||
Let's take the example of an application with both a *command* and an *event* bus. The application | ||
dispatches a command named ``RegisterUser`` to the command bus. The command is handled by the | ||
``RegisterUserHandler`` which creates a ``User`` object, stores that object to a database and | ||
dispatches a ``UserRegistered`` event to the event bus. | ||
dispatches a ``UserRegistered`` message to the event bus. | ||
|
||
There are many subscribers to the ``UserRegistered`` event, one subscriber may send | ||
There are many handlers to the ``UserRegistered`` message, one handler may send | ||
a welcome email to the new user. We are using the ``DoctrineTransactionMiddleware`` | ||
to wrap all database queries in one database transaction. | ||
|
||
|
@@ -33,88 +33,14 @@ Doctrine transaction, in which the user has been created. | |
**Problem 2:** If an exception is thrown when saving the user to the database, the welcome | ||
email is still sent because it is handled asynchronously. | ||
|
||
``DispatchAfterCurrentBusMiddleware`` Middleware | ||
------------------------------------------------ | ||
DispatchAfterCurrentBusMiddleware Middleware | ||
-------------------------------------------- | ||
|
||
For many applications, the desired behavior is to have any messages dispatched by the handler | ||
to `only` be handled after the handler finishes. This can be by using the | ||
For many applications, the desired behavior is to *only* handle messages that are | ||
dispatched by a handler once that handler has fully finished. This can be by using the | ||
``DispatchAfterCurrentBusMiddleware`` middleware and adding a ``DispatchAfterCurrentBusStamp`` | ||
stamp to `the message Envelope </components/messenger#adding-metadata-to-messages-envelopes>`_. | ||
|
||
Referencing the above example, this means that the ``UserRegistered`` event would not be handled | ||
until *after* the ``RegisterUserHandler`` had completed and the new ``User`` was persisted to the | ||
database. If the ``RegisterUserHandler`` encounters an exception, the ``UserRegistered`` event will | ||
never be handled and if an exception is thrown while sending the welcome email, the Doctrine | ||
transaction will not be rolled back. | ||
|
||
The ``dispatch_after_current_bus`` middleware is enabled by default. It is configured as the | ||
first middleware on all busses. When doing a highly custom or special configuration, then make | ||
sure ``dispatch_after_current_bus`` is registered before ``doctrine_transaction`` | ||
in the middleware chain. | ||
|
||
**Note:** The ``dispatch_after_current_bus`` middleware must be loaded for *all* of the | ||
buses. For the example, the middleware must be loaded for both the command and event bus. | ||
|
||
.. configuration-block:: | ||
|
||
.. code-block:: yaml | ||
|
||
# config/packages/messenger.yaml | ||
framework: | ||
messenger: | ||
default_bus: messenger.bus.command | ||
|
||
buses: | ||
messenger.bus.command: | ||
middleware: | ||
- validation | ||
messenger.bus.event: | ||
default_middleware: allow_no_handlers | ||
middleware: | ||
- validation | ||
|
||
.. code-block:: xml | ||
|
||
<!-- config/packages/messenger.xml --> | ||
<?xml version="1.0" encoding="UTF-8" ?> | ||
<container xmlns="http://symfony.com/schema/dic/services" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xmlns:framework="http://symfony.com/schema/dic/symfony" | ||
xsi:schemaLocation="http://symfony.com/schema/dic/services | ||
https://symfony.com/schema/dic/services/services-1.0.xsd"> | ||
|
||
<framework:config> | ||
<framework:messenger default_bus="messenger.bus.command"> | ||
<framework:bus name="messenger.bus.command"> | ||
<framework:middleware id="validation"> | ||
<framework:middleware id="doctrine_transaction"> | ||
</framework:bus> | ||
<framework:bus name="messenger.bus.command" default_middleware="allow_no_handlers"> | ||
<framework:middleware id="validation"> | ||
<framework:middleware id="doctrine_transaction"> | ||
</framework:bus> | ||
</framework:messenger> | ||
</framework:config> | ||
</container> | ||
|
||
.. code-block:: php | ||
|
||
// config/packages/messenger.php | ||
$container->loadFromExtension('framework', [ | ||
'messenger' => [ | ||
'default_bus' => 'messenger.bus.command', | ||
'buses' => [ | ||
'messenger.bus.command' => [ | ||
'middleware' => ['validation', 'doctrine_transaction'], | ||
], | ||
'messenger.bus.event' => [ | ||
'default_middleware' => 'allow_no_handlers', | ||
'middleware' => ['validation', 'doctrine_transaction'], | ||
], | ||
], | ||
], | ||
]); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code block didn't seem relevant anymore. I think it originally came from when |
||
.. code-block:: php | ||
|
||
namespace App\Messenger\CommandHandler; | ||
|
@@ -185,8 +111,20 @@ buses. For the example, the middleware must be loaded for both the command and e | |
} | ||
} | ||
|
||
This means that the ``UserRegistered`` message would not be handled | ||
until *after* the ``RegisterUserHandler`` had completed and the new ``User`` was persisted to the | ||
database. If the ``RegisterUserHandler`` encounters an exception, the ``UserRegistered`` event will | ||
never be handled. And if an exception is thrown while sending the welcome email, the Doctrine | ||
transaction will not be rolled back. | ||
|
||
.. note:: | ||
|
||
If ``WhenUserRegisteredThenSendWelcomeEmail`` throws an exception, that exception | ||
will be wrapped into a ``DelayedMessageHandlingException``. Using ``DelayedMessageHandlingException::getExceptions`` | ||
will give you all exceptions that are thrown while handing a message with the ``DispatchAfterCurrentBusStamp``. | ||
|
||
The ``dispatch_after_current_bus`` middleware is enabled by default. If you're | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 2 spaces after “The” |
||
configuring your middleware manually, be sure to register | ||
``dispatch_after_current_bus`` before ``doctrine_transaction`` in the middleware | ||
chain. Also, the ``dispatch_after_current_bus`` middleware must be loaded for *all* of | ||
the buses being used. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a bit tricky: in Messenger, messages are called messages. If you're using Messenger as an event bus, you can of course call them events. I'm mostly using the word "message" to keep consistent with messenger (and not confuse with the EventDispatcher), though I keep "event" wording in the code examples.