1
1
.. index ::
2
- single: Messenger; Record messages
2
+ single: Messenger; Record messages; Transaction messages
3
3
4
- Events Recorder : Handle Events After CommandHandler Is Done
5
- ===========================================================
4
+ Transactional Messages : Handle Events After CommandHandler Is Done
5
+ ==================================================================
6
6
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:
11
11
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
13
27
a welcome email to the new user. We are using the ``DoctrineTransactionMiddleware ``
14
28
to wrap all database queries in one database transaction.
15
29
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
17
31
will not be created because the ``DoctrineTransactionMiddleware `` will rollback the
18
32
Doctrine transaction, in which the user has been created.
19
33
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.
23
46
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 ``
25
54
middleware. Make sure it is registered before ``DoctrineTransactionMiddleware ``
26
55
in the middleware chain.
27
56
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
+
28
60
.. configuration-block ::
29
61
30
62
.. code-block :: yaml
@@ -33,45 +65,56 @@ in the middleware chain.
33
65
framework :
34
66
messenger :
35
67
default_bus : messenger.bus.command
68
+
36
69
buses :
37
70
messenger.bus.command :
38
71
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
43
75
messenger.bus.event :
76
+ default_middleware : allow_no_handlers
44
77
middleware :
45
- - messenger.middleware.allow_no_handler
46
- - messenger.middleware.validation
78
+ - validation
79
+ - handle_message_in_new_transaction
80
+ - doctrine_transaction
81
+
47
82
48
83
.. code-block :: php
49
84
50
85
namespace App\Messenger\CommandHandler;
51
86
52
87
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 ;
55
90
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;
57
94
58
- class CreateUserHandler
95
+ class SignUpUserHandler
59
96
{
60
97
private $em;
61
- private $eventRecorder ;
98
+ private $eventBus ;
62
99
63
- public function __construct(MessageRecorderInterface $eventRecorder , EntityManagerInterface $em)
100
+ public function __construct(MessageBusInterface $eventBus , EntityManagerInterface $em)
64
101
{
65
- $this->eventRecorder = $eventRecorder ;
102
+ $this->eventBus = $eventBus ;
66
103
$this->em = $em;
67
104
}
68
105
69
- public function __invoke(CreateUser $command)
106
+ public function __invoke(SignUpUser $command)
70
107
{
71
108
$user = new User($command->getUuid(), $command->getName(), $command->getEmail());
72
109
$this->em->persist($user);
73
110
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
+ );
76
119
}
77
120
}
0 commit comments