-
-
Notifications
You must be signed in to change notification settings - Fork 9.7k
Description
Q | A |
---|---|
Bug report? | no |
Feature request? | yes |
BC Break report? | no |
RFC? | yes |
Symfony version | 4.1 |
Context
Every day, more applications require messages going out of the inner context of the application. This could be because of a micro-service architecture, 3rd party dependencies or asynchronous processing. I believe we should have all the tools within Symfony to fulfil these needs.
Current attempts
#23650 offers to add an asynchronous processing to the EventDispatcher. Everybody more or less agrees that this isn't EventDispatcher's responsibility and this should be its own component.
#23315 offer to create two AMQP and Worker components. My personal point of view is that there are no real reason of being two components and that they are too specific. Also, we should leverage the experience being built within Swarrot or php-enqueue.
A Message component
The philosophy behind a Message component is that we can generalise a lot of use cases behind a few concepts:
-
Message
The user’s domain object. This won't require any specific interface. -
MessageBus
Middleware powered, it will be responsible of calling the correct message handlers. The middlewares adds an extension point that will be used to enforce rules by the user or libraries (for example, transactional consistency for commands within a CQRS application) -
MessageHandler
Will be called to handle the message. It can be one or more objects registered to handle the message. -
MessageConsumer
This will be responsible of getting the messages from a source (queuing system, command-line, HTTP interface, …) and dispatching it to the message bus. -
MessageProducer
Will be responsible of creating the message and sending it to a target (queuing system, task management system, HTTP interface, …) -
MessageEncoder and MessageDecoder
A way to encode and decode the user-land messages into something that is serializable/deserializable from the producers and consumers. The API should be something likeencode($message) : array
anddecode(array $encodedMessage) : mixed
I believe, to have a single representation (as an array).
The value in such mechanism is that this messaging component is generalised to allow the communication from/to applications (through an asynchronous queuing system or not).
The consumer abstraction
One of my favourite outcome of such component is the consumer abstraction. As a user, I will send a message to the bus and this message will either be handled by a "handler" directly within my application or sent to a "producer". This allows a smooth progression within the applications' architecture, enabling beginners to easily plug a queuing mechanism. Here is how I envisage the flow:
- User sends a message to the message bus
- A middleware knows how to get the handlers. If there are handlers for this message within the application, it will call them. If no handler, call the next middeware
- This next middleware knows how to get the correct producer for the message (could be a default one, and configuration powered). It will send the message to the middleware.
What will this component contain?
- A MessageBus with 2 default middlewares:
CallMessageHandlersMiddleware
SendMessageToProducersMiddleware
- An AMQP implementation of a consumer and producer based on
php-amqp
, not more. The other implementation should IMHO be in Swarrot and/or php-enqueue. - A
MessageBundle
bundle that will gives the following capabilities:- Register a handler with a
message_handler
tag - A
message:consume
command that will start a consumer
- Register a handler with a
- (Optional) A DataCollector to display the various sent/handled messages within the WDT and Profiler.
Usage
Without Symfony Bundle
$bus = new MessageBus([
new CallMessageHandlersMiddleware([
UserCreatedMessage::class => [new UserCreatedMessageHandler(/* ... */),],
]),
new SendMessageToProducersMiddleware([
UserMessageToBeSent::class => [new AMQPMessageProducer(/* ... */),],
]),
]);
$bus->handle(new UserCreatedMessage('...'));
Within Symfony
# framework.yaml
framework:
message_bus:
message_producer_mapping:
App\Message\UserMessageToBeSent: swarrot.publisher
# services.yaml
services:
App\Handler\UserCreatedMessageHandler:
tags: [{name: message_handler, handles: App\Message\UserCreatedMessage}]
I'd like to have a few weeks of discussions on the idea before starting to move the code I have from our message library and SimpleBus' MessageBus within a Component PR. Please feel free to challenge as much as possible so we have the best component for Symfony :)