8000 Symfony Messenger component documentation by sroze · Pull Request #9437 · symfony/symfony-docs · GitHub
[go: up one dir, main page]

Skip to content
8000

Symfony Messenger component documentation #9437

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 15 commits into from
Apr 16, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Update the documentation to reflect the latest changes
  • Loading branch information
sroze committed Apr 12, 2018
commit 32403eac9bdf6cb9f8a23440715d121276eee12d
29 changes: 16 additions & 13 deletions components/messenger.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,15 @@ Concepts
.. image:: /_images/components/messenger/overview.png

**Sender**:
Responsible for serializing and sending the message to _something_. This
Responsible for serializing and sending messages to _something_. This
something can be a message broker or a third party API for example.
Copy link
Contributor

Choose a reason for hiding this comment

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

a messager handler, broker or a third-party API.

Reasons:

  • The current phrasing kinda implies the component is very queue oriented. It's not wrong but it's not limited to this use case
  • Using it with a message handler is a case common enough to be mentioned IMO
  • I'm not sure all the people are familiar with queues and its related vocabulary so putting message handler which is likely to be more commonly known will make it more accessible


**Receiver**:
Responsible for deserializing and forwarding the messages to handler(s). This
Responsible for deserializing and forwarding messages to handler(s). This
can be a message queue puller or an API endpoint for example.

**Handler**:
Given a received message, contains the user business logic related to the
message. In practice, that is just a PHP callable.
Responsible for handling messages using the business logic applicable to the messages.

Bus
---
Expand Down Expand Up @@ -65,15 +64,14 @@ Example::

.. note:

Every middleware need to implement the ``MiddlewareInterface`` interface.
Every middleware needs to implement the ``MiddlewareInterface`` interface.

Handlers
--------

Once dispatched to the bus, messages will be handled by a "message handler". A
8000 Copy link

Choose a reason for hiding this comment

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

"dispatched" is confusing here: according to the Bus section, the bus is responsible for dispatching messages; and the method used to get a message on to the bus is handle().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, it's dispatch :)

message handler is a PHP callable (i.e. a function or an instance of a class)
Copy link
Contributor

Choose a reason for hiding this comment

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

that will do the required processing for your message. It _might_ return a
result::
that will do the required processing for your message::

namespace App\MessageHandler;

Expand All @@ -90,8 +88,8 @@ result::
Adapters
--------

The communication with queuing system or third parties is delegated to
libraries for now.
In order to send and receive messages, you will have to configure an adapter. An
adapter will be responsible of communicating with your message broker or 3rd parties.

Your own sender
~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -169,18 +167,23 @@ First, create your receiver::
$this->filePath = $filePath;
}

public function receive() : \Generator
public function receive(callable $handler) : void
{
$ordersFromCsv = $this->serializer->deserialize(file_get_contents($this->filePath), 'csv');

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

public function stop(): void
{
// noop
}
}

Same bus received and sender
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Receiver and Sender on the same bus
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

To allow us to receive and send messages on the same bus and prevent an infinite
loop, the message bus is equipped with the ``WrapIntoReceivedMessage`` middleware.
Expand Down
94 changes: 32 additions & 62 deletions messenger.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ you need it, like in a controller::
// src/Controller/DefaultController.php
namespace App\Controller;

use App\Message\SendNotification;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Messenger\MessageBusInterface;

Copy link

Choose a reason for hiding this comment

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

Please add something like use App\Message\SendNotification; to make clear that SendNotification is not provided by the messenger component.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added 👍

Expand Down Expand Up @@ -91,7 +92,7 @@ the messenger component, the following configuration should have been created:
framework:
messenger:
adapters:
default: "%env(MESSENGER_DSN)%"
amqp: "%env(MESSENGER_DSN)%"

.. code-block:: bash

Expand All @@ -100,17 +101,17 @@ the messenger component, the following configuration should have been created:
MESSENGER_DSN=amqp://guest:guest@localhost:5672/%2f/messages
###< symfony/messenger ###

This is enough to allow you to route your message to the ``messenger.default_adapter``
adapter. This will also configure the following for you:
This is enough to allow you to route your message to the ``amqp``. This will also
configure the following services for you:

1. A ``messenger.default_sender`` sender to be used when routing messages
2. A ``messenger.default_receiver`` receiver to be used when consuming messages.
1. A ``messenger.sender.amqp`` sender to be used when routing messages.
2. A ``messenger.receiver.amqp`` receiver to be used when consuming messages.

Routing
-------

Instead of calling a handler, you have the option to route your message(s) to a
sender. Part of an adapter, it is responsible of sending your message somewhere.
sender. Part of an adapter, it is responsible for sending your message somewhere.
You can configure which message is routed to which sender with the following
configuration:

Expand All @@ -119,40 +120,39 @@ configuration:
framework:
messenger:
routing:
'My\Message\Message': messenger.default_sender # Or another sender service name
'My\Message\Message': amqp # The name of the defined adapter

Such configuration would only route the ``My\Message\Message`` message to be
asynchronous, the rest of the messages would still be directly handled.
Copy link

Choose a reason for hiding this comment

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

Instead of two sentences, one before the config example and one after, how about just one:-

"The following configuration shows how you can route a class of messages to a sender, leaving other classes of messages to be passed to their respective handlers."

Copy link

Choose a reason for hiding this comment

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

Also, routing to messenger.default_sender seems to be wrong:-

In CheckExceptionOnInvalidReferenceBehaviorPass.php line 32:
                                                                               
  [Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException]   
  The service "messenger.asynchronous.middleware.send_message_to_producer" ha  
  s a dependency on a non-existent service "messenger.default_sender".

It should be messenger.sender.default.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's less clear with a single example IMHO. I've updated the sender configuration 👍


If you want to do route all the messages to a queue by default, you can use such
configuration:
You can route all classes of message to a sender using an asterisk instead of a class name:

.. code-block:: yaml

framework:
messenger:
routing:
'My\Message\MessageAboutDoingOperationalWork': messenger.operations_sender
'*': messenger.default_sender
'My\Message\MessageAboutDoingOperationalWork': another_adapter
'*': amqp

Note that you can also route a message to multiple senders at the same time:
A class of message can also be routed to a multiple senders by specifying a list:
Copy link

Choose a reason for hiding this comment

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

- A class of message can also be routed to a multiple senders by specifying a list:
+ A class of message can also be routed to multiple senders by specifying a list:


.. code-block:: yaml

framework:
messenger:
routing:
'My\Message\ToBeSentToTwoSenders': [messenger.default_sender, messenger.audit_sender]
'My\Message\ToBeSentToTwoSenders': [amqp, audit]

Last but not least you can also route a message while still calling the handler
on your application by having a ``null`` sender:
By specifying a ``null`` sender, you can also route a class of messages to a sender
while still having them passed to their respective handler:

.. code-block:: yaml

framework:
messenger:
routing:
'My\Message\ThatIsGoingToBeSentAndHandledLocally': [messenger.default_sender, ~]
'My\Message\ThatIsGoingToBeSentAndHandledLocally': [amqp, ~]

Consuming messages
------------------
Expand All @@ -163,81 +163,51 @@ like this:

.. code-block:: terminal

$ bin/console messenger:consume-messages messenger.default_receiver
$ bin/console messenger:consume-messages amqp

The first argument is the receiver's service name. It might have been created by
your ``adapters`` configuration or it can be your own receiver.

Registering your middleware
---------------------------

The message bus is based on a set of middleware. If you are un-familiar with the concept,
look at the :doc:`Messenger component docs </components/messenger>`.

To register your middleware, use the ``messenger.middleware`` tag as in the
following example:

.. code-block:: xml

<service id="Your\Own\Middleware">
<tag name="messenger.middleware" />
</service>

Your own Adapters
-----------------

Learn how to build your own adapters within the Component's documentation. Once
you have built your classes, you can register your adapter factory to be able to
use it via a DSN in the Symfony application.
Once you have written your adapter's sender and receiver, you can register your
adapter factory to be able to use it via a DSN in the Symfony application.

Create your adapter Factory
~~~~~~~~~~~~~~~~~~~~~~~~~~~

You need to give FrameworkBundle the opportunity to create your adapter from a
DSN. You will need an adapter factory::

use Symfony\Component\Messenger\Adapter\Factory\AdapterInterface;
use Symfony\Component\Messenger\Adapter\Factory\AdapterFactoryInterface;

class YourAdapterFactory implements AdapterFactoryInterface
{
public function create(string $dsn): AdapterInterface
{
return new YourAdapter(/* ... */);
}

public function supports(string $dsn): bool
{
return 0 === strpos($dsn, 'my-adapter://');
}
}

The ``YourAdaper`` class need to implement the ``AdapterInterface``. It
will like the following example::

use Symfony\Component\Messenger\Adapter\Factory\AdapterInterface;
use Symfony\Component\Messenger\Transport\ReceiverInterface;
use Symfony\Component\Messenger\Transport\SenderInterface;

class YourAdapter implements AdapterInterface
class YourAdapterFactory implements AdapterFactoryInterface
{
public function receiver(): ReceiverInterface
public function createReceiver(string $dsn, array $options): ReceiverInterface
{
return new YourReceiver(/* ... */);
}

public function sender(): SenderInterface
public function createSender(string $dsn, array $options): SenderInterface
{
return new YourSender(/* ... */);
}

public function supports(string $dsn, array $options): bool
{
return 0 === strpos($dsn, 'my-adapter://');
}
}

Register your factory
Copy link
Member

Choose a reason for hiding this comment

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

s/f/F/

~~~~~~~~~~~~~~~~~~~~~

.. code-block:: xml

<service id="Your\Adapter\Factory">
<service id="Your\Adapter\YourAdapterFactory">
<tag name="messenger.adapter_factory" />
</service>

Expand All @@ -254,10 +224,10 @@ named adapter using your own DSN:
adapters:
yours: 'my-adapter://...'

This will give you access to the following services:
In addition of being able to route your messages to the ``yours`` sender, this
will give you access to the following services:

1. ``messenger.yours_adapter``: the instance of your adapter.
2. ``messenger.yours_receiver`` and ``messenger.yours_sender``, the
receiver and sender created by the adapter.
1. ``messenger.sender.hours``: the sender.
2. ``messenger.receiver.hours``: the receiver.

.. _`enqueue's adapter`: https://github.com/sroze/enqueue-bridge
0