Skip to content

[Messenger] Add the Envelope in the documentation #9757

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

Merged
merged 4 commits into from
Jul 12, 2018
Merged
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 69 additions & 7 deletions components/messenger.rst
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,63 @@ that will do the required processing for your message::
}
}

Adding Metadata to Messages (Envelopes)
---------------------------------------

If you need to add metadata or some configuration to a message, wrap it with the
:class:`Symfony\\Component\\Messenger\\Envelope` class. For example, to set the
serialization groups used when the message goes through the transport layer, use
the ``SerializerConfiguration`` envelope::

use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Transport\Serialization\SerializerConfiguration;

$bus->dispatch(
(new Envelope($message))->with(new SerializerConfiguration([
'groups' => ['my_serialization_groups'],
]))
);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could mention others built-in configurations available (just ValidationConfiguration for now) here or in another section.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just did 👍

At the moment, the Symfony Messenger has the following built-in envelopes:

1. :class:`Symfony\\Component\\Messenger\\Transport\\Serialization\\SerializerConfiguration`,
to configure the serialization groups used by the transport.
2. :class:`Symfony\\Component\\Messenger\\Middleware\\Configuration\\ValidationConfiguration`,
to configure the validation groups used when the validation middleware is enabled.
3. :class:`Symfony\\Component\\Messenger\\Asynchronous\\Transport\\ReceivedMessage`,
an internal item that marks the message as received from a transport.

Instead of dealing directly with the messages in the middleware you can receive the
envelope by implementing the :class:`Symfony\\Component\\Messenger\\EnvelopeAwareInterface`
marker, like this::

use Symfony\Component\Messenger\Asynchronous\Transport\ReceivedMessage;
use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
use Symfony\Component\Messenger\EnvelopeAwareInterface;

class MyOwnMiddleware implements MiddlewareInterface, EnvelopeAwareInterface
{
public function handle($message, callable $next)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, maybe we can rename $message to $envelope in this example? PHP allows us to do so (i.e. variable names not to match with the name in the interface). It might be clearer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Please rename this.

{
// $message here is an `Envelope` object, because this middleware
// implements the EnvelopeAwareInterface interface.

if (null !== $message->get(ReceivedMessage::class)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this get() method something that lives on Envelope? Isn't there always just one message inside an Envelope? If so, why do we pass an argument here? Or, could you also pass something like $message->get(SerializerConfiguration::class) to get those types of things out?

Also, suppose some similar code: if $message->get(MyMessage::class) is null, does it mean that this message is NOT originally a MyMessage isntance, right? I mean, it's equivalent to !$message instanceof MyMessage in a middleware without EnvelopAwareInterface?

Some inline comments with some of these answers might be all we need :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, is it a real use-case to look need to know whether or not a message was just received? If you're using an async transport and you want to do something for a specific message type, is it important that you make sure this processing is done only when your message has been "received"? If so, why? And if so, how could I both perform my action only when the message is "received" AND only check the class of the original message (e.g. I need to do something when my MyMessage class is "received"? Or is that totally not the correct pattern.

Copy link
Contributor Author

@sroze sroze Jun 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if $message->get(MyMessage::class) is null, does it mean that this message is NOT originally a MyMessage isntance, right?

There is mis interpretation here. The ReceivedMessage class is not an example here, it's an actual envelope item within the component has is a marker that represents that the message has just been received. It's needed to have the router middleware ignoring them (otherwise we would have a receive -> send) loop.

Or, could you also pass something like $message->get(SerializerConfiguration::class) to get those types of things out?

That's not also, that's the only thing you can do :)

how could I both perform my action only when the message is "received" AND only check the class of the original message

if (nul !== $message->get(ReceivedMessage::class) && $message->getMessage() instanceof YourOwnMessageClass) {
    // Your logic...
}

Might be clearer if we rename $message to $envelope?

// Message just has been received...

// You could for example add another item.
$message = $message->with(new AnotherEnvelopeItem(/* ... */));
}

return $next($message);
}
}

The above example will forward the message to the next middleware with an additional
envelope item if the message has just been received (i.e. has the `ReceivedMessage` item).
You can create your own items by implementing the :class:`Symfony\\Component\\Messenger\\EnvelopeAwareInterface`
interface.

Transports
----------

Expand All @@ -115,6 +172,7 @@ First, create your sender::

use App\Message\ImportantAction;
use Symfony\Component\Messenger\Transport\SenderInterface;
use Symfony\Component\Messenger\Envelope;

class ImportantActionToEmailSender implements SenderInterface
{
Expand All @@ -127,10 +185,12 @@ First, create your sender::
$this->toEmail = $toEmail;
}

public function send($message)
public function send(Envelope $envelope)
{
$message = $envelope->getMessage();

if (!$message instanceof ImportantAction) {
throw new \InvalidArgumentException(sprintf('Producer only supports "%s" messages.', ImportantAction::class));
throw new \InvalidArgumentException(sprintf('This transport only supports "%s" messages.', ImportantAction::class));
}

$this->mailer->send(
Expand Down Expand Up @@ -165,6 +225,7 @@ First, create your receiver::
use App\Message\NewOrder;
use Symfony\Component\Messenger\Transport\ReceiverInterface;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Messenger\Envelope;

class NewOrdersFromCsvFile implements ReceiverInterface
{
Expand All @@ -182,7 +243,9 @@ First, create your receiver::
$ordersFromCsv = $this->serializer->deserialize(file_get_contents($this->filePath), 'csv');

foreach ($ordersFromCsv as $orderFromCsv) {
$handler(new NewOrder($orderFromCsv['id'], $orderFromCsv['account_id'], $orderFromCsv['amount']));
$order = new NewOrder($orderFromCsv['id'], $orderFromCsv['account_id'], $orderFromCsv['amount']);

$handler(new Envelope($order));
}
}

Expand All @@ -196,10 +259,9 @@ Receiver and Sender on the same Bus
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

To allow sending and receiving messages on the same bus and prevent an infinite
loop, the message bus is equipped with the ``WrapIntoReceivedMessage`` middleware.
It will wrap the received messages into ``ReceivedMessage`` objects and the
``SendMessageMiddleware`` middleware will know it should not route these
messages again to a transport.
loop, the message bus will add a :class:`Symfony\\Component\\Messenger\\Asynchronous\\Transport\\ReceivedMessage`
envelope item to the message envelopes and the :class:`Symfony\\Component\\Messenger\\Asynchronous\\Middleware\\SendMessageMiddleware`
middleware will know it should not route these messages again to a transport.

.. _blog posts about command buses: https://matthiasnoback.nl/tags/command%20bus/
.. _SimpleBus project: http://simplebus.io