Category: Architecture
Symfony Messenger - in steps
When Symfony developers talk about queues in Symfony, they usualy talk about Messenger. Symfony Messenger is not a queue system, it is message handling system able to communicate with queue systems. In the case of Messenger, these are called Transports. And some Transports can internally implement queues. For example Doctrine or AMQP (RabbitMQ).
This may be just semantical difference, but to me this helps to understand the role and architecture of this package. And also how to approach and explain its flow of work in few steps.
-
message -> message handler
- message - is plain serializable object. Think of it as DTO with scalar values, Enums or array of scalars.
- message handler - class implementing handling logic of the message. Usually in
_invoke(Message $message)method.
This is the core logic of Messenger. You may better imagine it as Command (message) and CommandHandler (message handler). For example
AddProductis simple DTO containing product id, title, details... and thenAddProductHandlerwill handle this command to execute all required actions to add product to cart.This simpe logic can be implemented even without using the Messenger package. When you are trying to prepare legacy system for Messenger, it may act as a first step.
-
message -> message bus -> message handler
- message bus - is using
dispatch(Message|Envelope $message)method to dispatch message to be processed by message handler. Message is wrapped inEnvelopeobject and there are addedStampsby middlewares in the process. We will get to these in step 3.
Simplest form of message bus would be just to pair messages with message handlers, as key value pairs, where message FQCN is the key and message handler FQCN is the value.
Messenger is bit more robust in how to detect pairing between messages and message handlers and is additionally processing messages through configured middlewares.
- multiple buses - more complex applications usually use multiple buses to make quick distinction in general type of messages. Most common is division to commands, events and queries, mainly because of applied middlewares.
command -> command bus -> command handler event -> event bus -> event handler query -> query bus -> query handler
- bus middlewares - in the Messenger bus is collection of middlewares applied to a message, this can be for example Validation. With multiple buses this is more visible:
- command bus - will have Validation middleware
- event bus - may need router_context middleware
- query bus - may need to strip default middlewares like failed_message_processing or dispatch_after_current_bus, to be faster in retrieving messages
You can check default collection of middlewares applied to a message in documentation.
- message bus - is using
-
message -> message bus -> transport (sync, async) -> message bus (async) -> message handler
- transport - by default Symfony is using Sync Transport. This means that messages will be processed immediately by handler. This can be configured very easily to one of predefined async Transports, like DB (Doctrine), Redis, AMQP... or even custom implementation. Transport can use specific
Stampsas a guidance what to do with the message, e.g. send message to delayed or priority queue. - message bus (async) - Transport will usually react on added Stamps to the message, but will not store these stamps. In Messenger the message recieved from async Transport is passed back to message bus to message bus to apply middlewares and
Stampsagain.
Which transport to use for which message is defined by Symfony message routing config.
You can also (bind handler to a specific Transport)[https://symfony.com/doc/current/messenger.html#binding-handlers-to-different-transports], if needed.
- transport - by default Symfony is using Sync Transport. This means that messages will be processed immediately by handler. This can be configured very easily to one of predefined async Transports, like DB (Doctrine), Redis, AMQP... or even custom implementation. Transport can use specific