diff --git a/components/messenger.rst b/components/messenger.rst index a784cce16f7..bbd6ecff7d3 100644 --- a/components/messenger.rst +++ b/components/messenger.rst @@ -38,7 +38,7 @@ Concepts Bus --- -The bus is used to dispatch messages. The behaviour of the bus is in its ordered +The bus is used to dispatch messages. The behavior of the bus is in its ordered middleware stack. The component comes with a set of middleware that you can use. When using the message bus with Symfony's FrameworkBundle, the following middleware @@ -63,9 +63,9 @@ Example:: $result = $bus->dispatch(new MyMessage(/* ... */)); -.. note: +.. note:: - Every middleware needs to implement the ``MiddlewareInterface`` interface. + Every middleware needs to implement the ``MiddlewareInterface``. Handlers -------- @@ -89,16 +89,16 @@ that will do the required processing for your message:: Transports ---------- -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. +In order to send and receive messages, you will have to configure a transport. A +transport will be responsible for communicating with your message broker or 3rd parties. -Your own sender +Your own Sender ~~~~~~~~~~~~~~~ Using the ``SenderInterface``, you can easily create your own message sender. -Let's say you already have an ``ImportantAction`` message going through the -message bus and handled by a handler. Now, you also want to send this message as -an email. +Imagine that you already have an ``ImportantAction`` message going through the +message bus and being handled by a handler. Now, you also want to send this +message as an email. First, create your sender:: @@ -135,13 +135,13 @@ First, create your sender:: } } -Your own receiver +Your own Receiver ~~~~~~~~~~~~~~~~~ A receiver is responsible for receiving messages from a source and dispatching them to the application. -Let's say you already processed some "orders" in your application using a +Imagine you already processed some "orders" in your application using a ``NewOrder`` message. Now you want to integrate with 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. @@ -183,10 +183,10 @@ First, create your receiver:: } } -Receiver and Sender on the same bus +Receiver and Sender on the same Bus ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -To allow us to receive and send messages on the same bus and prevent an infinite +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 diff --git a/messenger.rst b/messenger.rst index ed73d148ae2..1a8fdb0a9ed 100644 --- a/messenger.rst +++ b/messenger.rst @@ -4,7 +4,7 @@ How to Use the Messenger ======================== -Symfony's Messenger provide a message bus and some routing capabilities to send +Symfony's Messenger provides a message bus and some routing capabilities to send messages within your application and through transports such as message queues. Before using it, read the :doc:`Messenger component docs ` to get familiar with its concepts. @@ -59,11 +59,38 @@ message handler. It's a class with an ``__invoke`` method:: Once you've created your handler, you need to register it: -.. code-block:: xml +.. configuration-block:: - - - + .. code-block:: yaml + + # config/services.yaml + services: + App\MessageHandler\MyMessageHandler: + tags: [messenger.message_handler] + + .. code-block:: xml + + + + + + + + + + + + + .. code-block:: php + + // config/services.php + use App\MessageHandler\MyMessageHandler; + + $container->register(MyMessageHandler::class) + ->addTag('messenger.message_handler'); .. note:: @@ -73,26 +100,58 @@ Once you've created your handler, you need to register it: Transports ---------- -The communication with queuing system or third parties is delegated to +The communication with queuing systems or third parties is delegated to 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 transport`_ + If you need more message brokers, you should have a look at `Enqueue's transport`_ which supports things like Kafka, Amazon SQS or Google Pub/Sub. 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: -.. code-block:: yaml - - # config/packages/messenger.yaml - framework: - messenger: - transports: - amqp: "%env(MESSENGER_DSN)%" +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/messenger.yaml + framework: + messenger: + transports: + amqp: "%env(MESSENGER_DSN)%" + + .. code-block:: xml + + + + + + + + + + + + + .. code-block:: php + + // config/packages/messenger.php + $container->loadFromExtension('framework', array( + 'messenger' => array( + 'transports' => array( + 'amqp' => '%env(MESSENGER_DSN)%', + ), + ), + )); .. code-block:: bash @@ -101,11 +160,11 @@ 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 ``amqp``. This will also -configure the following services for you: +This is enough to allow you to route your message to the ``amqp`` transport. +This will also 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. +#. A ``messenger.sender.amqp`` sender to be used when routing messages; +#. A ``messenger.receiver.amqp`` receiver to be used when consuming messages. .. note:: @@ -124,46 +183,191 @@ sender. Part of a transport, it is responsible for sending your message somewher You can configure which message is routed to which sender with the following configuration: -.. code-block:: yaml - - framework: - messenger: - routing: - 'My\Message\Message': amqp # The name of the defined transport +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/messenger.yaml + framework: + messenger: + routing: + 'My\Message\Message': amqp # The name of the defined transport + + .. code-block:: xml + + + + + + + + + + + + + + + .. code-block:: php + + // config/packages/messenger.php + $container->loadFromExtension('framework', array( + 'messenger' => array( + 'routing' => array( + 'My\Message\Message' => 'amqp', + ), + ), + )); Such configuration would only route the ``My\Message\Message`` message to be asynchronous, the rest of the messages would still be directly handled. -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': another_transport - '*': amqp - -A class of message can also be routed to multiple senders by specifying a list: - -.. code-block:: yaml - - framework: - messenger: - routing: - 'My\Message\ToBeSentToTwoSenders': [amqp, audit] +You can route all classes of messages to the same sender using an asterisk +instead of a class name: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/messenger.yaml + framework: + messenger: + routing: + 'My\Message\MessageAboutDoingOperationalWork': another_transport + '*': amqp + + .. code-block:: xml + + + + + + + + + + + + + + + + + + .. code-block:: php + + // config/packages/messenger.php + $container->loadFromExtension('framework', array( + 'messenger' => array( + 'routing' => array( + 'My\Message\Message' => 'another_transport', + '*' => 'amqp', + ), + ), + )); + +A class of messages can also be routed to multiple senders by specifying a list: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/messenger.yaml + framework: + messenger: + routing: + 'My\Message\ToBeSentToTwoSenders': [amqp, audit] + + .. code-block:: xml + + + + + + + + + + + + + + + .. code-block:: php + + // config/packages/messenger.php + $container->loadFromExtension('framework', array( + 'messenger' => array( + 'routing' => array( + 'My\Message\ToBeSentToTwoSenders' => array('amqp', 'audit'), + ), + ), + )); 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': [amqp, ~] - -Consuming messages +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/messenger.yaml + framework: + messenger: + routing: + 'My\Message\ThatIsGoingToBeSentAndHandledLocally': [amqp, ~] + + .. code-block:: xml + + + + + + + + + + + + + + + .. code-block:: php + + // config/packages/messenger.php + $container->loadFromExtension('framework', array( + 'messenger' => array( + 'routing' => array( + 'My\Message\ThatIsGoingToBeSentAndHandledLocally' => array('amqp', null), + ), + ), + )); + +Consuming Messages ------------------ Once your messages have been routed, you will like to consume your messages in most @@ -177,50 +381,104 @@ like this: The first argument is the receiver's service name. It might have been created by your ``transports`` configuration or it can be your own receiver. -Multiple buses +Multiple Buses -------------- -If you are interested into architectures like CQRS, you might want to have multiple +If you are interested in 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 +You can create multiple buses (in this example, a command bus 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: ~ +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/messenger.yaml + framework: + messenger: + # The bus that is going to be injected when injecting MessageBusInterface: + default_bus: messenger.bus.commands + + # Create buses + buses: + messenger.bus.commands: ~ + messenger.bus.events: ~ + + .. code-block:: xml + + + + + + + + + + + + + .. code-block:: php + + // config/packages/messenger.php + $container->loadFromExtension('framework', array( + 'messenger' => array( + 'default_bus' => 'messenger.bus.commands', + 'buses' => array( + 'messenger.bus.commands' => null, + 'messenger.bus.events' => null, + ), + ), + )); This will generate the ``messenger.bus.commands`` and ``messenger.bus.events`` services that you can inject in your services. -Type-hints and auto-wiring +Type-hints and Auto-wiring ~~~~~~~~~~~~~~~~~~~~~~~~~~ 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. +the auto-wiring will not work as it won't know which bus to inject in your own services. 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 +.. configuration-block:: + + .. code-block:: yaml + + # config/services.yaml + services: + _defaults: + # ... + + bind: + $commandBus: '@messenger.bus.commands' + $eventBus: '@messenger.bus.events' - # config/services.yaml - services: - _defaults: - # ... + .. code-block:: xml - bind: - $commandBus: '@messenger.bus.commands' - $eventBus: '@messenger.bus.events' + + + + + + + + + + + Middleware ---------- @@ -229,49 +487,123 @@ 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. +#. ``logging`` middleware. Responsible for logging the beginning and the end of the + message within the bus; -2. _Your own collection of middleware_ +#. _Your own collection of middleware_; -3. ``route_messages`` middleware. Will route the messages your configured to their - corresponding sender and stop the middleware chain. +#. ``route_messages`` middleware. Will route the messages you configured to their + corresponding sender and stop the middleware chain; -4. ``call_message_handler`` middleware. Will call the message handler(s) for the +#. ``call_message_handler`` middleware. Will call the message handler(s) for the given message. -Adding your own middleware +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 - - 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 +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/messenger.yaml + framework: + messenger: + buses: + messenger.bus.default: + middleware: + - 'App\Middleware\MyMiddleware' + - 'App\Middleware\AnotherMiddleware' + + .. code-block:: xml + + + + + + + + + + + + + + + .. code-block:: php + + // config/packages/messenger.php + $container->loadFromExtension('framework', array( + 'messenger' => array( + 'buses' => array( + 'messenger.bus.default' => array( + 'middleware' => array( + 'App\Middleware\MyMiddleware', + 'App\Middleware\AnotherMiddleware', + ), + ), + ), + ), + )); + +Note that if the service is abstract, a different instance of service will be created per bus. -Disabling default middleware +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 +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/messenger.yaml + framework: + messenger: + buses: + messenger.bus.default: + default_middleware: false + + .. code-block:: xml + + + + + + + + + + + + .. code-block:: php + + // config/packages/messenger.php + $container->loadFromExtension('framework', array( + 'messenger' => array( + 'buses' => array( + 'messenger.bus.default' => array( + 'default_middleware' => false, + ), + ), + ), + )); Your own Transport ------------------ @@ -283,7 +615,7 @@ Create your Transport Factory ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You need to give FrameworkBundle the opportunity to create your transport from a -DSN. You will need an transport factory:: +DSN. You will need a transport factory:: use Symfony\Component\Messenger\Transport\TransportFactoryInterface; use Symfony\Component\Messenger\Transport\TransportInterface; @@ -303,9 +635,8 @@ DSN. You will need an transport factory:: } } -The transport object is needs to implements the ``TransportInterface`` (which simply combine -the ``SenderInterface`` and ``ReceiverInterface``). It will look -like this:: +The transport object needs to implement the ``TransportInterface`` (which simply combines +the ``SenderInterface`` and ``ReceiverInterface``). It will look like this:: class YourTransport implements TransportInterface { @@ -325,32 +656,92 @@ like this:: } } -Register your factory +Register your Factory ~~~~~~~~~~~~~~~~~~~~~ -.. code-block:: xml +.. configuration-block:: - - - + .. code-block:: yaml -Use your transport + # config/services.yaml + services: + Your\Transport\YourTransportFactory: + tags: [messenger.transport_factory] + + .. code-block:: xml + + + + + + + + + + + + + .. code-block:: php + + // config/services.php + use Your\Transport\YourTransportFactory; + + $container->register(YourTransportFactory::class) + ->setTags(array('messenger.transport_factory')); + +Use your Transport ~~~~~~~~~~~~~~~~~~ Within the ``framework.messenger.transports.*`` configuration, create your named transport using your own DSN: -.. code-block:: yaml - - framework: - messenger: - transports: - yours: 'my-transport://...' +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/messenger.yaml + framework: + messenger: + transports: + yours: 'my-transport://...' + + .. code-block:: xml + + + + + + + + + + + + + .. code-block:: php + + // config/packages/messenger.php + $container->loadFromExtension('framework', array( + 'messenger' => array( + 'transports' => array( + '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.sender.yours``: the sender; #. ``messenger.receiver.yours``: the receiver. .. _`enqueue's transport`: https://github.com/enqueue/messenger-adapter