Skip to content

[Messenger] Update the messenger documentation #9727

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 11 commits into from
May 9, 2018
10 changes: 5 additions & 5 deletions components/messenger.rst
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,11 @@ that will do the required processing for your message::
}
}

Adapters
--------
Transports
----------

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.
In order to send and receive messages, you will have to configure a transport. An
transport will be responsible of communicating with your message broker or 3rd parties.

Your own sender
~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -190,4 +190,4 @@ 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.
It will wrap the received messages into ``ReceivedMessage`` objects and the
``SendMessageMiddleware`` middleware will know it should not route these
messages again to an adapter.
messages again to a transport.
197 changes: 160 additions & 37 deletions messenger.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ How to Use the Messenger
========================

Symfony's Messenger provide a message bus and some routing capabilities to send
messages within your application and through adapters such as message queues.
messages within your application and through transports such as message queues.
Before using it, read the :doc:`Messenger component docs </components/messenger>`
to get familiar with its concepts.

Expand Down Expand Up @@ -44,7 +44,7 @@ Registering Handlers
--------------------

In order to do something when your message is dispatched, you need to create a
message handler. It's a class with an `__invoke` method::
message handler. It's a class with an ``__invoke`` method::

// src/MessageHandler/MyMessageHandler.php
namespace App\MessageHandler;
Expand All @@ -70,19 +70,19 @@ Once you've created your handler, you need to register it:
If the message cannot be guessed from the handler's type-hint, use the
``handles`` attribute on the tag.

Adapters
--------
Transports
----------

The communication with queuing system or third parties is delegated to
libraries for now. The built-in AMQP adapter allows you to communicate with
libraries for now. The built-in AMQP transport allows you to communicate with
most of the AMQP brokers such as RabbitMQ.

.. note::

If you need more message brokers, you should have a look to `Enqueue's adapter`_
If you need more message brokers, you should have a look to `Enqueue's transport`_
which supports things like Kafka, Amazon SQS or Google Pub/Sub.

An adapter is registered using a "DSN", which is a string that represents the
A transport is registered using a "DSN", which is a string that represents the
connection credentials and configuration. By default, when you've installed
the messenger component, the following configuration should have been created:

Expand All @@ -91,7 +91,7 @@ the messenger component, the following configuration should have been created:
# config/packages/messenger.yaml
framework:
messenger:
adapters:
transports:
amqp: "%env(MESSENGER_DSN)%"

.. code-block:: bash
Expand All @@ -107,11 +107,20 @@ configure the following services for you:
1. A ``messenger.sender.amqp`` sender to be used when routing messages.
2. A ``messenger.receiver.amqp`` receiver to be used when consuming messages.

.. note::

In order to use Symfony's built-in AMQP transport, you will need the Serializer
Component. Ensure that it is installed with:

.. code-block:: terminal

$ composer require symfony/serializer-pack

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 for sending your message somewhere.
sender. Part of a transport, it is responsible for sending your message somewhere.
You can configure which message is routed to which sender with the following
configuration:

Expand All @@ -120,7 +129,7 @@ configuration:
framework:
messenger:
routing:
'My\Message\Message': amqp # The name of the defined adapter
'My\Message\Message': amqp # The name of the defined transport

Such configuration would only route the ``My\Message\Message`` message to be
asynchronous, the rest of the messages would still be directly handled.
Expand All @@ -132,7 +141,7 @@ You can route all classes of message to a sender using an asterisk instead of a
framework:
messenger:
routing:
'My\Message\MessageAboutDoingOperationalWork': another_adapter
'My\Message\MessageAboutDoingOperationalWork': another_transport
'*': amqp

A class of message can also be routed to multiple senders by specifying a list:
Expand Down Expand Up @@ -166,39 +175,153 @@ like this:
$ 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.
your ``transports`` configuration or it can be your own receiver.

Multiple buses
--------------

If you are interested into architectures like CQRS, you might want to have multiple
buses within your application.

You can create multiple buses (in this example, a command and an event bus) like
this:

.. code-block:: yaml

framework:
messenger:
# The bus that is going to be injected when injecting MessageBusInterface:
default_bus: commands

# Create buses
buses:
messenger.bus.commands: ~
messenger.bus.events: ~

Your own Adapters
-----------------
This will generate the ``messenger.bus.commands`` and ``messenger.bus.events`` services
that you can inject in your services.

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.
Type-hints and auto-wiring
~~~~~~~~~~~~~~~~~~~~~~~~~~

Create your adapter Factory
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Auto-wiring is a great feature that allows you to reduce the amount of configuration
required for your service container to be created. When using multiple buses, by default,
the auto-wiring will not work as it won't know why bus to inject in your own services.

You need to give FrameworkBundle the opportunity to create your adapter from a
DSN. You will need an adapter factory::
In order to clarify this, you can use the DependencyInjection's binding capabilities
to clarify which bus will be injected based on the argument's name:

.. code-block:: yaml

# config/services.yaml
services:
_defaults:
# ...

bind:
$commandBus: '@messenger.bus.commands'
$eventBus: '@messenger.bus.events'

Middleware
----------

What happens when you dispatch a message to a message bus(es) depends on its
collection of middleware (and their order). By default, the middleware configured
for each bus looks like this:

1. ``logging`` middleware. Responsible of logging the beginning and the end of the
message within the bus.

2. _Your own collection of middleware_

3. ``route_messages`` middleware. Will route the messages your configured to their
corresponding sender and stop the middleware chain.

4. ``call_message_handler`` middleware. Will call the message handler(s) for the
given message.

Adding your own middleware
~~~~~~~~~~~~~~~~~~~~~~~~~~

As described in the component documentation, you can add your own middleware
within the buses to add some extra capabilities like this:

.. code-block:: yaml

use Symfony\Component\Messenger\Adapter\Factory\AdapterFactoryInterface;
framework:
messenger:
buses:
messenger.bus.default:
middleware:
- 'App\Middleware\MyMiddleware'
- 'App\Middleware\AnotherMiddleware'

Note that if the service is abstract, then a different instance of service will be
created per bus.

Disabling default middleware
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If you don't want the default collection of middleware to be present on your bus,
you can disable them like this:

.. code-block:: yaml

framework:
messenger:
buses:
messenger.bus.default:
default_middleware: false

Your own Transport
------------------

Once you have written your transport's sender and receiver, you can register your
transport factory to be able to use it via a DSN in the Symfony application.

Create your Transport Factory
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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

use Symfony\Component\Messenger\Transport\TransportFactoryInterface;
use Symfony\Component\Messenger\Transport\TransportInterface;
use Symfony\Component\Messenger\Transport\ReceiverInterface;
use Symfony\Component\Messenger\Transport\SenderInterface;

class YourAdapterFactory implements AdapterFactoryInterface
class YourTransportFactory implements TransportFactoryInterface
{
public function createReceiver(string $dsn, array $options): ReceiverInterface
public function createTransport(string $dsn, array $options): TransportInterface
{
return new YourReceiver(/* ... */);
return new YourTransport(/* ... */);
}

public function createSender(string $dsn, array $options): SenderInterface
public function supports(string $dsn, array $options): bool
{
return new YourSender(/* ... */);
return 0 === strpos($dsn, 'my-transport://');
}
}

public function supports(string $dsn, array $options): bool
The transport object is needs to implements the ``TransportInterface`` (which simply combine
the ``SenderInterface`` and ``ReceiverInterface``). It will look
like this::

class YourTransport implements TransportInterface
{
public function send($message) : void
{
// ...
}

public function receive(callable $handler) : void
{
// ...
}

public function stop() : void
{
return 0 === strpos($dsn, 'my-adapter://');
// ...
}
}

Expand All @@ -207,27 +330,27 @@ Register your factory

.. code-block:: xml

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

Use your adapter
~~~~~~~~~~~~~~~~
Use your transport
~~~~~~~~~~~~~~~~~~

Within the ``framework.messenger.adapters.*`` configuration, create your
named adapter using your own DSN:
Within the ``framework.messenger.transports.*`` configuration, create your
named transport using your own DSN:

.. code-block:: yaml

framework:
messenger:
adapters:
yours: 'my-adapter://...'
transports:
yours: 'my-transport://...'

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

#. ``messenger.sender.yours``: the sender.
#. ``messenger.receiver.yours``: the receiver.

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