From b4e25a18824a21afcf2288101942d5d83ef9f411 Mon Sep 17 00:00:00 2001 From: Samuel ROZE Date: Thu, 10 May 2018 14:29:03 +0100 Subject: [PATCH 1/7] Add a documentation about Messenger's transports --- components/messenger.rst | 6 +- components/messenger/transports.rst | 225 ++++++++++++++++++++++++++++ 2 files changed, 230 insertions(+), 1 deletion(-) create mode 100644 components/messenger/transports.rst diff --git a/components/messenger.rst b/components/messenger.rst index d1da1e2ff31..889ac1227ce 100644 --- a/components/messenger.rst +++ b/components/messenger.rst @@ -196,6 +196,10 @@ Transports 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. +.. note: + + Check out the :doc:`Messenger transports documentation `. + Your own Sender ~~~~~~~~~~~~~~~ @@ -299,7 +303,7 @@ First, create your receiver:: Receiver and Sender on the same Bus ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -To allow sending and receiving messages on the same bus and prevent an infinite +In order to receive and send messages on the same bus and prevent an infinite loop, the message bus will add a :class:`Symfony\\Component\\Messenger\\Stamp\\ReceivedStamp` stamp to the message envelopes and the :class:`Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware` middleware will know it should not route these messages again to a transport. diff --git a/components/messenger/transports.rst b/components/messenger/transports.rst new file mode 100644 index 00000000000..e09ce895f3b --- /dev/null +++ b/components/messenger/transports.rst @@ -0,0 +1,225 @@ +.. index:: + single: Messenger; Transports + +Transports +========== + +To send and receive messages via message brokers, the Messenger component has +"transports". Transports are responsible for routing messages to and from +the message brokers. + +Every transport is configurable using a DSN. This DSN allows you to chose the +transport layer as well as configure it. + +AMQP +---- + +The most famous message broker protocol is probably AMQP, which is most +commonly implemented with RabbitMQ. The Messenger component has built-in +support for AMQP. + +How it works? +~~~~~~~~~~~~~ + +A DSN protocol that uses ``amqp`` is recognized and sent to the built-in AMQP transport. +For example:: + + $dsn = 'amqp://user:password@localhost/%2f/messages' + +The messages will be sent to the ``messages`` exchange bound to the ``messages`` +queue on the ``/`` vhost (the ``%2f`` is a url-encoded ``/``). + +.. note: + + By default, RabbitMQ uses ``guest`` as a username and ``guest`` as a password + and has a ``/`` vhost. + +Error handling +~~~~~~~~~~~~~~ + +If something wrong happens (i.e. an exception is thrown) while handling your message, +the default behaviour is that your message will be "NACK" and requeued. + +However, if your exception implements the ``RejectMessageExceptionInterface`` interface, +the message will be rejected from the queue. + +Retry +~~~~~ + +When receiving a message from a broker, it might happen that some exceptions will +arise. Typically, a 3rd party provider is down or your system is under heavy load +and can't really process some messages. To handle this scenario, there is a built-in +retry mechanism that can be enabled via your DSN:: + + amqp://guest:guest@localhost/%2f/messages + ?retry[attempts]=3 + &retry[ttl][0]=10000 + &retry[ttl][1]=30000 + &retry[ttl][2]=60000 + +In the example above, if handling the message fails, it will retry it 3 times. After +the first failure, it will wait 10 seconds before trying it. After the 2nd failure, +it will wait 30 seconds. After the 3rd failure, it will wait a minute. If it still +fails, the message will be moved to a "dead queue" and you will have to manually +handle this message. + +DSN configuration reference +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The options available in the DSN are documented on the ``Connection`` class +in the code repository. + +Enqueue +------- + +Probably one of the most famous PHP queue-broker libraries, Enqueue, has 10+ adapters +with brokers like Kafka, Google Pub/Sub, AWS SQS and more. Check out the transport +documentation in `Enqueue's official repository`_. + +Your own transport +------------------ + +If there is no available transport for your message broker, you can easily +create your own. + +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. + +First, create your sender:: + + namespace App\MessageSender; + + use App\Message\ImportantAction; + use Symfony\Component\Messenger\Transport\SenderInterface; + + class ImportantActionToEmailSender implements SenderInterface + { + private $toEmail; + private $mailer; + + public function __construct(\Swift_Mailer $mailer, string $toEmail) + { + $this->mailer = $mailer; + $this->toEmail = $toEmail; + } + + public function send($message) + { + if (!$message instanceof ImportantAction) { + throw new \InvalidArgumentException(sprintf('Producer only supports "%s" messages.', ImportantAction::class)); + } + + $this->mailer->send( + (new \Swift_Message('Important action made')) + ->setTo($this->toEmail) + ->setBody( + '

Important action

Made by '.$message->getUsername().'

', + 'text/html' + ) + ); + } + } + +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 +``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. + +You will read this CSV file and dispatch a ``NewOrder`` message. All you need to +do is to write your custom CSV receiver and Symfony will do the rest. + +First, create your receiver:: + + namespace App\MessageReceiver; + + use App\Message\NewOrder; + use Symfony\Component\Messenger\Transport\ReceiverInterface; + use Symfony\Component\Serializer\SerializerInterface; + + class NewOrdersFromCsvFile implements ReceiverInterface + { + private $serializer; + private $filePath; + + public function __construct(SerializerInteface $serializer, string $filePath) + { + $this->serializer = $serializer; + $this->filePath = $filePath; + } + + public function receive(callable $handler) : void + { + $ordersFromCsv = $this->serializer->deserialize(file_get_contents($this->filePath), 'csv'); + + foreach ($ordersFromCsv as $orderFromCsv) { + $handler(new NewOrder($orderFromCsv['id'], $orderFromCsv['account_id'], $orderFromCsv['amount'])); + } + } + + public function stop(): void + { + // noop + } + } + +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 YourTransportFactory implements TransportFactoryInterface + { + public function createTransport(string $dsn, array $options): TransportInterface + { + return new YourTransport(/* ... */); + } + + public function supports(string $dsn, array $options): bool + { + return 0 === strpos($dsn, 'my-transport://'); + } + } + +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 + { + // ... + } + } + +If you plan to use it within a Symfony application, you should look at +:doc:`registering your transport factory ` with the FrameworkBundle. + +.. _`Enqueue's official repository`: https://github.com/enqueue/messenger-adapter From cf224cd5fb03ea47897fd29a541d36c0b169661b Mon Sep 17 00:00:00 2001 From: Olena Kirichok Date: Sun, 7 Apr 2019 15:18:36 +0200 Subject: [PATCH 2/7] fixup! Add a documentation about Messenger's transports --- components/messenger.rst | 4 ++-- components/messenger/transports.rst | 20 ++++++++------------ 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/components/messenger.rst b/components/messenger.rst index 889ac1227ce..1c94b5022cd 100644 --- a/components/messenger.rst +++ b/components/messenger.rst @@ -196,7 +196,7 @@ Transports 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. -.. note: +.. seealso:: Check out the :doc:`Messenger transports documentation `. @@ -303,7 +303,7 @@ First, create your receiver:: Receiver and Sender on the same Bus ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -In order to receive and send messages on the same bus and prevent an infinite +To receive and send messages on the same bus and prevent an infinite loop, the message bus will add a :class:`Symfony\\Component\\Messenger\\Stamp\\ReceivedStamp` stamp to the message envelopes and the :class:`Symfony\\Component\\Messenger\\Middleware\\SendMessageMiddleware` middleware will know it should not route these messages again to a transport. diff --git a/components/messenger/transports.rst b/components/messenger/transports.rst index e09ce895f3b..203c0afc417 100644 --- a/components/messenger/transports.rst +++ b/components/messenger/transports.rst @@ -21,15 +21,15 @@ support for AMQP. How it works? ~~~~~~~~~~~~~ -A DSN protocol that uses ``amqp`` is recognized and sent to the built-in AMQP transport. -For example:: +A DSN that starts with ``amqp://`` is recognized and used to create +an instance of the built-in AMQP transport:: - $dsn = 'amqp://user:password@localhost/%2f/messages' + $dsn = 'amqp://user:password@localhost/%2f/messages'; The messages will be sent to the ``messages`` exchange bound to the ``messages`` queue on the ``/`` vhost (the ``%2f`` is a url-encoded ``/``). -.. note: +.. note:: By default, RabbitMQ uses ``guest`` as a username and ``guest`` as a password and has a ``/`` vhost. @@ -58,7 +58,7 @@ retry mechanism that can be enabled via your DSN:: &retry[ttl][2]=60000 In the example above, if handling the message fails, it will retry it 3 times. After -the first failure, it will wait 10 seconds before trying it. After the 2nd failure, +the first failure, it will wait 10 seconds before trying it. After the second failure, it will wait 30 seconds. After the 3rd failure, it will wait a minute. If it still fails, the message will be moved to a "dead queue" and you will have to manually handle this message. @@ -88,9 +88,7 @@ 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. - -First, create your sender:: +an email via your sender:: namespace App\MessageSender; @@ -137,9 +135,7 @@ application but you can't use an API and need to use a shared CSV file with new orders. You will read this CSV file and dispatch a ``NewOrder`` message. All you need to -do is to write your custom CSV receiver and Symfony will do the rest. - -First, create your receiver:: +do is to write your custom CSV receiver and Symfony will do the rest:: namespace App\MessageReceiver; @@ -177,7 +173,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; From 7ad2123d76c9c17a38ff06b9c2d2a8246bcf10c0 Mon Sep 17 00:00:00 2001 From: Olena Kirichok Date: Sun, 28 Apr 2019 20:05:43 +0200 Subject: [PATCH 3/7] fixup! Add a documentation about Messenger's transports --- components/messenger/transports.rst | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/components/messenger/transports.rst b/components/messenger/transports.rst index 203c0afc417..16628106afe 100644 --- a/components/messenger/transports.rst +++ b/components/messenger/transports.rst @@ -8,7 +8,7 @@ To send and receive messages via message brokers, the Messenger component has "transports". Transports are responsible for routing messages to and from the message brokers. -Every transport is configurable using a DSN. This DSN allows you to chose the +Every transport is configurable using a DSN. This DSN allows you to choose the transport layer as well as configure it. AMQP @@ -18,7 +18,7 @@ The most famous message broker protocol is probably AMQP, which is most commonly implemented with RabbitMQ. The Messenger component has built-in support for AMQP. -How it works? +How Does it Works? ~~~~~~~~~~~~~ A DSN that starts with ``amqp://`` is recognized and used to create @@ -34,11 +34,11 @@ queue on the ``/`` vhost (the ``%2f`` is a url-encoded ``/``). By default, RabbitMQ uses ``guest`` as a username and ``guest`` as a password and has a ``/`` vhost. -Error handling +Error Handling ~~~~~~~~~~~~~~ If something wrong happens (i.e. an exception is thrown) while handling your message, -the default behaviour is that your message will be "NACK" and requeued. +the default behavior is that your message will be "NACK" and requeued. However, if your exception implements the ``RejectMessageExceptionInterface`` interface, the message will be rejected from the queue. @@ -51,6 +51,7 @@ arise. Typically, a 3rd party provider is down or your system is under heavy loa and can't really process some messages. To handle this scenario, there is a built-in retry mechanism that can be enabled via your DSN:: +.. code-block:: bash amqp://guest:guest@localhost/%2f/messages ?retry[attempts]=3 &retry[ttl][0]=10000 @@ -63,7 +64,7 @@ it will wait 30 seconds. After the 3rd failure, it will wait a minute. If it sti fails, the message will be moved to a "dead queue" and you will have to manually handle this message. -DSN configuration reference +DSN Configuration Reference ~~~~~~~~~~~~~~~~~~~~~~~~~~~ The options available in the DSN are documented on the ``Connection`` class @@ -76,20 +77,23 @@ Probably one of the most famous PHP queue-broker libraries, Enqueue, has 10+ ada with brokers like Kafka, Google Pub/Sub, AWS SQS and more. Check out the transport documentation in `Enqueue's official repository`_. -Your own transport +Your own Transport ------------------ -If there is no available transport for your message broker, you can easily -create your own. +If there is no available transport for your message broker, you can create your own. -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 +Using the ``SenderInterface``, you can create your own message sender. +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 via your sender:: +.. code-block:: php + + // src/MessageSender/ImportantActionToEmailSender.php + namespace App\MessageSender; use App\Message\ImportantAction; @@ -129,7 +133,7 @@ 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 +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. From 38f76c64b94ec9fc1cc1d536d0a9acb8cc1c0f63 Mon Sep 17 00:00:00 2001 From: Olena Kirichok Date: Sun, 28 Apr 2019 20:09:02 +0200 Subject: [PATCH 4/7] fixup! Add a documentation about Messenger's transports --- components/messenger/transports.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/messenger/transports.rst b/components/messenger/transports.rst index 16628106afe..01ecc4b58f5 100644 --- a/components/messenger/transports.rst +++ b/components/messenger/transports.rst @@ -19,7 +19,7 @@ commonly implemented with RabbitMQ. The Messenger component has built-in support for AMQP. How Does it Works? -~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~ A DSN that starts with ``amqp://`` is recognized and used to create an instance of the built-in AMQP transport:: From 9055f2fb53deb6d8e2adf947776728f3cff03075 Mon Sep 17 00:00:00 2001 From: Olena Kirichok Date: Sun, 28 Apr 2019 20:14:33 +0200 Subject: [PATCH 5/7] fixup! Add a documentation about Messenger's transports --- components/messenger/transports.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/messenger/transports.rst b/components/messenger/transports.rst index 01ecc4b58f5..9538a6a409f 100644 --- a/components/messenger/transports.rst +++ b/components/messenger/transports.rst @@ -49,7 +49,7 @@ Retry When receiving a message from a broker, it might happen that some exceptions will arise. Typically, a 3rd party provider is down or your system is under heavy load and can't really process some messages. To handle this scenario, there is a built-in -retry mechanism that can be enabled via your DSN:: +retry mechanism that can be enabled via your DSN: .. code-block:: bash amqp://guest:guest@localhost/%2f/messages From 547971df7d4ab2b9c6e7d605f2527077d9450e4f Mon Sep 17 00:00:00 2001 From: Olena Kirichok Date: Sun, 28 Apr 2019 20:36:23 +0200 Subject: [PATCH 6/7] fixup! Add a documentation about Messenger's transports --- components/messenger/transports.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/messenger/transports.rst b/components/messenger/transports.rst index 9538a6a409f..5d3040c7d20 100644 --- a/components/messenger/transports.rst +++ b/components/messenger/transports.rst @@ -49,9 +49,8 @@ Retry When receiving a message from a broker, it might happen that some exceptions will arise. Typically, a 3rd party provider is down or your system is under heavy load and can't really process some messages. To handle this scenario, there is a built-in -retry mechanism that can be enabled via your DSN: +retry mechanism that can be enabled via your DSN:: -.. code-block:: bash amqp://guest:guest@localhost/%2f/messages ?retry[attempts]=3 &retry[ttl][0]=10000 From 83b26c5a67c175af8bc073d04ee756b45e3797eb Mon Sep 17 00:00:00 2001 From: Olena Kirichok Date: Sun, 28 Apr 2019 20:40:02 +0200 Subject: [PATCH 7/7] fixup! Add a documentation about Messenger's transports --- components/messenger/transports.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/messenger/transports.rst b/components/messenger/transports.rst index 5d3040c7d20..4e91137ff36 100644 --- a/components/messenger/transports.rst +++ b/components/messenger/transports.rst @@ -87,7 +87,7 @@ Your own Sender Using the ``SenderInterface``, you can create your own message sender. 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 via your sender:: +an email via your sender: .. code-block:: php