-
-
Notifications
You must be signed in to change notification settings - Fork 5.2k
[Messenger] Added documentation about DispatchAfterCurrentBusMiddleware #10015
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
Closed
Closed
Changes from 12 commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
8107340
Added documentation about message recorder
Nyholm 66fc600
According to feedback
Nyholm 091040f
a user
Nyholm 0a3ccf6
Added fixes
Nyholm b5921bf
Explain HandleMessageInNewTransaction middleware
gubler aba0edf
Minor updates
Nyholm 9b589ff
Added PHP and XML config
Nyholm 0e1e0dd
Update messenger/message-recorder.rst
OskarStark aa4096f
Update messenger/message-recorder.rst
OskarStark 636de55
Update messenger/message-recorder.rst
OskarStark 157d3f0
Update messenger/message-recorder.rst
OskarStark 6b71ef8
updates according to feedback
Nyholm 45245df
minor updates according to feedback
Nyholm File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
.. index:: | ||
single: Messenger; Record messages; Transaction messages | ||
|
||
Transactional Messages: Handle Events After CommandHandler is Done | ||
================================================================== | ||
|
||
A message handler can ``dispatch`` new messages during execution, to either the same or | ||
a different bus (if the application has `multiple buses </messenger/multiple_buses>`_). | ||
Any errors or exceptions that occur during this process can have unintended consequences, | ||
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 the dispatched message can still | ||
be handled even if the original handler encounters an exception. | ||
Nyholm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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. | ||
|
||
There are many subscribers to the ``UserRegistered`` event, one subscriber may send | ||
a welcome email to the new user. We are using the ``DoctrineTransactionMiddleware`` | ||
to wrap all database queries in one database transaction. | ||
|
||
**Problem 1:** If an exception is thrown when sending the welcome email, then the user | ||
will not be created because the ``DoctrineTransactionMiddleware`` will rollback the | ||
Doctrine transaction, in which the user has been created. | ||
Nyholm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
**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 | ||
------------------------------------------------ | ||
|
||
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 | ||
``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 | ||
- doctrine_transaction | ||
messenger.bus.event: | ||
default_middleware: allow_no_handlers | ||
middleware: | ||
- validation | ||
- doctrine_transaction | ||
Nyholm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
.. 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'], | ||
], | ||
], | ||
], | ||
]); | ||
|
||
.. code-block:: php | ||
|
||
namespace App\Messenger\CommandHandler; | ||
|
||
use App\Entity\User; | ||
use App\Messenger\Command\RegisterUser; | ||
use App\Messenger\Event\UserRegistered; | ||
use Doctrine\ORM\EntityManagerInterface; | ||
Nyholm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
use Symfony\Component\Messenger\Envelope; | ||
use Symfony\Component\Messenger\Stamp\DispatchAfterCurrentBusStamp; | ||
use Symfony\Component\Messenger\MessageBusInterface; | ||
|
||
class RegisterUserHandler | ||
{ | ||
private $eventBus; | ||
private $em; | ||
|
||
public function __construct(MessageBusInterface $eventBus, EntityManagerInterface $em) | ||
{ | ||
$this->eventBus = $eventBus; | ||
$this->em = $em; | ||
} | ||
|
||
public function __invoke(RegisterUser $command) | ||
{ | ||
$user = new User($command->getUuid(), $command->getName(), $command->getEmail()); | ||
$this->em->persist($user); | ||
Nyholm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// The DispatchAfterCurrentBusStamp marks the event message to be handled | ||
// only if this handler does not throw an exception. | ||
|
||
$event = new UserRegistered($command->getUuid()); | ||
$this->eventBus->dispatch( | ||
(new Envelope($event)) | ||
->with(new DispatchAfterCurrentBusStamp()) | ||
); | ||
Nyholm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
|
||
.. code-block:: php | ||
|
||
namespace App\Messenger\EventSubscriber; | ||
|
||
use App\Entity\User; | ||
use App\Messenger\Event\UserRegistered; | ||
use Doctrine\ORM\EntityManagerInterface; | ||
Nyholm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
use Symfony\Component\Mailer\MailerInterface; | ||
use Symfony\Component\Mime\RawMessage; | ||
|
||
class WhenUserRegisteredThenSendWelcomeEmail | ||
{ | ||
private $mailer; | ||
private $em; | ||
|
||
public function __construct(MailerInterface $mailer, EntityManagerInterface $em) | ||
{ | ||
$this->mailer = $mailer; | ||
$this->em = $em; | ||
} | ||
|
||
public function __invoke(UserRegistered $event) | ||
{ | ||
$user = $this->em->getRepository(User::class)->find(new User($event->getUuid())); | ||
|
||
$this->mailer->send(new RawMessage('Welcome '.$user->getFirstName())); | ||
} | ||
} | ||
|
||
.. 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``. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
@javiereguiluz s/After/after/ ?