Skip to content

Commit 585491d

Browse files
committed
Move the documentation from the main PR to RST format.
1 parent 989d594 commit 585491d

File tree

2 files changed

+235
-0
lines changed

2 files changed

+235
-0
lines changed
21.9 KB
Loading

components/message.rst

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
.. index::
2+
single: Message
3+
single: Components; Message
4+
5+
The Message Component
6+
=====================
7+
8+
The Message component helps application to send and receive messages
9+
to/from other applications or via
10+
11+
Concepts
12+
--------
13+
14+
.. image:: /_images/components/message/overview.png
15+
16+
1. **Sender**
17+
Responsible for serializing and sending the message to _something_. This something can be a message broker or a 3rd
18+
party API for example.
19+
20+
2. **Receiver**
21+
Responsible for deserializing and forwarding the messages to handler(s). This can be a message queue puller or an API
22+
endpoint for example.
23+
24+
3. **Handler**
25+
Given a received message, contains the user business logic related to the message. In practice, that is just a PHP
26+
callable.
27+
28+
Bus
29+
---
30+
31+
The bus is used to dispatch messages. MessageBus' behaviour is in its ordered middleware stack. When using
32+
the message bus with Symfony's FrameworkBundle, the following middlewares are configured for you:
33+
34+
1. `LoggingMiddleware` (log the processing of your messages)
35+
2. `SendMessageMiddleware` (enable asynchronous processing)
36+
3. `HandleMessageMiddleware` (call the registered handle)
37+
38+
use App\Message\MyMessage;
39+
40+
$result = $this->get('message_bus')->handle(new MyMessage(/* ... */));
41+
42+
Handlers
43+
--------
44+
45+
Once dispatched to the bus, messages will be handled by a "message handler". A message handler is a PHP callable
46+
(i.e. a function or an instance of a class) that will do the required processing for your message. It _might_ return a
47+
result.
48+
49+
namespace App\MessageHandler;
50+
51+
use App\Message\MyMessage;
52+
53+
class MyMessageHandler
54+
{
55+
public function __invoke(MyMessage $message)
56+
{
57+
// Message processing...
58+
}
59+
}
60+
61+
62+
<service id="App\Handler\MyMessageHandler">
63+
<tag name="message_handler" />
64+
</service>
65+
66+
**Note:** If the message cannot be guessed from the handler's type-hint, use the `handles` attribute on the tag.
67+
68+
### Asynchronous messages
69+
70+
Using the Message Component is useful to decouple your application but it also very useful when you want to do some
71+
asychronous processing. This means that your application will produce a message to a queuing system and consume this
72+
message later in the background, using a _worker_.
73+
74+
#### Adapters
75+
76+
The communication with queuing system or 3rd parties is for delegated to libraries for now. You can use one of the
77+
following adapters:
78+
79+
- [PHP Enqueue bridge](https://github.com/sroze/enqueue-bridge) to use one of their 10+ compatible queues such as
80+
RabbitMq, Amazon SQS or Google Pub/Sub.
81+
82+
Routing
83+
-------
84+
85+
When doing asynchronous processing, the key is to route the message to the right sender. As the routing is
86+
application-specific and not message-specific, the configuration can be made within the `framework.yaml`
87+
configuration file as well:
88+
89+
framework:
90+
message:
91+
routing:
92+
'My\Message\MessageAboutDoingOperationalWork': my_operations_queue_sender
93+
94+
Such configuration would only route the `MessageAboutDoingOperationalWork` message to be asynchronous, the rest of the
95+
messages would still be directly handled.
96+
97+
If you want to do route all the messages to a queue by default, you can use such configuration:
98+
99+
framework:
100+
message:
101+
routing:
102+
'My\Message\MessageAboutDoingOperationalWork': my_operations_queue_sender
103+
'*': my_default_sender
104+
105+
Note that you can also route a message to multiple senders at the same time:
106+
107+
framework:
108+
message:
109+
routing:
110+
'My\Message\AnImportantMessage': [my_default_sender, my_audit_sender]
111+
112+
Same bus received and sender
113+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
114+
115+
To allow us to receive and send messages on the same bus and prevent a loop, the message bus is equipped with the
116+
`WrapIntoReceivedMessage` received. It will wrap the received messages into `ReceivedMessage` objects and the
117+
`SendMessageMiddleware` middleware will know it should not send these messages.
118+
119+
Your own sender
120+
---------------
121+
122+
Using the `SenderInterface`, you can easily create your own message sender. Let's say you already have an
123+
`ImportantAction` message going through the message bus and handled by a handler. Now, you also want to send this
124+
message as an email.
125+
126+
1. Create your sender
127+
128+
namespace App\MessageSender;
129+
130+
use Symfony\Component\Message\SenderInterface;
131+
use App\Message\ImportantAction;
132+
133+
class ImportantActionToEmailSender implements SenderInterface
134+
{
135+
private $toEmail;
136+
private $mailer;
137+
138+
public function __construct(\Swift_Mailer $mailer, string $toEmail)
139+
{
140+
$this->mailer = $mailer;
141+
$this->toEmail = $toEmail;
142+
}
143+
144+
public function send($message)
145+
{
146+
if (!$message instanceof ImportantAction) {
147+
throw new \InvalidArgumentException(sprintf('Producer only supports "%s" messages.', ImportantAction::class));
148+
}
149+
150+
$this->mailer->send(
151+
(new \Swift_Message('Important action made'))
152+
->setTo($this->toEmail)
153+
->setBody(
154+
'<h1>Important action</h1><p>Made by '.$message->getUsername().'</p>',
155+
'text/html'
156+
)
157+
);
158+
}
159+
}
160+
161+
2. Register your sender service
162+
163+
services:
164+
App\MessageSender\ImportantActionToEmailSender:
165+
arguments:
166+
- "@mailer"
167+
- "%to_email%"
168+
169+
tags:
170+
- message.sender
171+
172+
3. Route your important message to the sender
173+
174+
framework:
175+
message:
176+
routing:
177+
'App\Message\ImportantAction': [App\MessageSender\ImportantActionToEmailSender, ~]
178+
179+
**Note:** this example shows you how you can at the same time send your message and directly handle it using a `null`
180+
(`~`) sender.
181+
182+
Your own receiver
183+
-----------------
184+
185+
A consumer is responsible of receiving messages from a source and dispatching them to the application.
186+
187+
Let's say you already proceed some "orders" on your application using a `NewOrder` message. Now you want to integrate with
188+
a 3rd party or a legacy application but you can't use an API and need to use a shared CSV file with new orders.
189+
190+
You will read this CSV file and dispatch a `NewOrder` message. All you need to do is your custom CSV consumer and Symfony will do the rest.
191+
192+
1. Create your receiver
193+
194+
namespace App\MessageReceiver;
195+
196+
use Symfony\Component\Message\ReceiverInterface;
197+
use Symfony\Component\Serializer\SerializerInterface;
198+
199+
use App\Message\NewOrder;
200+
201+
class NewOrdersFromCsvFile implements ReceiverInterface
202+
{
203+
private $serializer;
204+
private $filePath;
205+
206+
public function __construct(SerializerInteface $serializer, string $filePath)
207+
{
208+
$this->serializer = $serializer;
209+
$this->filePath = $filePath;
210+
}
211+
212+
public function receive() : \Generator
213+
{
214+
$ordersFromCsv = $this->serializer->deserialize(file_get_contents($this->filePath), 'csv');
215+
216+
foreach ($ordersFromCsv as $orderFromCsv) {
217+
yield new NewOrder($orderFromCsv['id'], $orderFromCsv['account_id'], $orderFromCsv['amount']);
218+
}
219+
}
220+
}
221+
222+
2. Register your receiver service
223+
224+
services:
225+
App\MessageReceiver\NewOrdersFromCsvFile:
226+
arguments:
227+
- "@serializer"
228+
- "%new_orders_csv_file_path%"
229+
230+
tags:
231+
- message.receiver
232+
233+
3. Use your consumer
234+
235+
$ bin/console message:consume App\MessageReceived\NewOrdersFromCsvFile

0 commit comments

Comments
 (0)