-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Messenger] Automatically route messages when from_transport
tag is set
#35111
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
Conversation
When you have multiple transports, it can become quite cumbersome to map every message to all the transports that it needs. Instead, you can tag each handler with `from_transport` to explicitly say on which transport it should handle. Having both the transport and the message that the handler will support, we can automatically add the routing too.
from_transport
tag is setfrom_transport
tag is set
framework:
messenger:
transports:
events:
dsn: amqp://guest:guest@127.0.0.1/%2f/events
options:
queues:
3rdparty:
binding_keys: [ 3rdparty ]
projection:
binding_keys: [ projection ]
events_3rdparty: amqp://guest:guest@127.0.0.1/%2f/events?queues[3rdparty]
events_projection: amqp://guest:guest@127.0.0.1/%2f/events?queues[projection]
routing:
'App\Message\RegisterBet': events Above config allows us publish messages to the Am I right that since your changes message will be automatically published to all of this transports? cc @sroze |
@Koc clever trick of separating the write and read. Didn't know this was possible. My change will only affect handlers that are tagged with Domain\Subscriber\Asynchronous\:
resource: '%kernel.project_dir%/src/Domain/Subscriber/Asynchronous'
tags:
- { name: 'messenger.message_handler', bus: 'event.bus', from_transport: 'async' }
Domain\Subscriber\Synchronous\:
resource: '%kernel.project_dir%/src/Domain/Subscriber/Synchronous'
tags:
- { name: 'messenger.message_handler', bus: 'event.bus', from_transport: 'sync' } Before this change, you would need to add all the messages (events in this case) to the routing and map them to the correct transport(s). Now, the routing will be automatically discovered based on Example:
With the config above, If you remove the |
@sroze I would like to know how you feel about this and if you considered this but didn't do for a good reason. |
@ruudk this is a very good idea. However, we need to make sure that just adding one handler with a
I think that this would be a BC-break if this was turned on by default configuration because it might be that an application developer decided to configure a We have two ways going forward with this:
I'd go for option 1, given that this behaviour would be optional, like the auto-wiring. Any PoV @symfony/mergers? |
@sroze thanks for your reply. What if we just make it possible to enable this behavior by tagging the handler with |
Sorry for asking. But isnt this the exact feature of a MessageSubscriberInterface? |
@Nyholm No, AFAIK not. It will register the handler to the correct messages and transports but it will not add entries to |
I see, so using I like that =) I dont believe we need an opt-in. If you currently not using any routing (and using Could you update your PR description with some examples? |
Hm.. I see that Samuel thinks it should be opt-in... Since some people may have turned of handlers in the wrong way.. fair. I suggest adding a new option: |
@Nyholm I have implemented what we discussed and I have updated the opening post with a lot of examples. Please let me know if you have any suggestions to make the opening post more clear for a future blog post by @javiereguiluz 😊 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you.
I think you made a typo in the tests, otherwise Im happy with this.
@@ -1046,6 +1046,10 @@ private function addMessengerSection(ArrayNodeDefinition $rootNode) | |||
->ifTrue(static function ($v): bool { return isset($v['buses']) && null !== $v['default_bus'] && !isset($v['buses'][$v['default_bus']]); }) | |||
->then(static function (array $v): void { throw new InvalidConfigurationException(sprintf('The specified default bus "%s" is not configured. Available buses are "%s".', $v['default_bus'], implode('", "', array_keys($v['buses'])))); }) | |||
->end() | |||
->booleanNode('auto_routing') | |||
->info('Automatically create routing for handlers that specify "from_transport" via tag attribute or MessageSubscriberInterface.') | |||
->defaultFalse() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, I think this should be defaultTrue()
in Symfony 6. But that is a separate discussion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How and where do you keep track of this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure. I wanted to write a note here to help other reviewers to pitch in. They will either say to open second PR/issue after this get merged so we can discuss if this is a good default or not, OR they will ask you to do something clever and add deprecation messages somewhere.
src/Symfony/Component/Messenger/Tests/DependencyInjection/MessengerPassTest.php
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The use-case for the from_transport
option is when you have multiple handlers for the same message and want to only execute certain handlers based on the transport.
This use-case totally conflicts with auto routing as you introduced here.
Say you have Message A, Handler B (from transport X) and Handler C (from transport Y). Auto routing would send the message A to both transports always? Then there is no point in having the from_transport option configured in the first place.
So those concepts conflict and should not be mixed. So I'm -1 on this PR.
I agree that making the routing easier would be nice. But not by misusing from_transport option.
What we need is a way to configure the routing, which is for sending, on the message (e.g. by implementing a certain interface). The handler configuration is for the receiving and not the right place.
Not as I have understood it. If A comes from transport X till till go to B, if it comes from Y it will go to C. If A comes from Z, no handler will be called. |
That's not the question. The question is about the routing to the sender which this PR is about. |
Messenger is built around sending/routing "messages" not "handlers". This makes the use-case of (A) a handler might not be called (because you forgot to route the class to an additional transport) For example, if a message has 1 handler today (with no What I like about this PR is that it aims to solve that: by (in theory) relying only on For example, if you have What would I propose instead? I'm not sure... but something that keeps the routing in one place. Maybe something like this? framework:
messenger:
routing:
# internally, this would send to async & sync + set the from_transport on the handlers
# more advanced users could still route by class and set the `from_transport` specifically
# on the handlers if they have more advanced config
'App\Command\CreateProjectCommand':
App\CommandHandler\CreateProjectHandler1: async
App\CommandHandler\CreateProjectHandler2: sync
'App\Command\DeleteProjectCommand': async To me, that is the clearest. However, it's also the most verbose - it's more long classes in YAML :). We could, thus, perhaps have an "auto" feature on a class-by-class basis: framework:
messenger:
routing:
'App\Command\CreateProjectCommand':
# would do the behavior of this PR: route based on `from_transport` of handlers
# I guess every handler should be required to have `from_transport`?
auto_route: true
'App\Command\DeleteProjectCommand': async This isn't bad... but it still requires every class to be routed... or to use In the end, I don't really like any of this. I get the feeling that the best solution might be something quite different... that I can't imagine right now. Ultimately, sending and receiving are two separate (but often linked) things. Maybe this PR is the right direction... I'm just not sure... |
When using multiple transports with Symfony Messenger, you need to add routing for every message to the designated transport(s).
This looks something like this:
As your project grows, this list can become very long. And it's yet another step that a developer needs to take to let the message be handled properly.
Messenger supports a way to explicitly bind handlers to a given transport. That can now also be enabled via tag attributes:
As you can see the
DeleteProjectHandler
is executed asynchronously because it's located in thesrc/Handler/Asynchronous
directory and tagged withfrom_transport: async
attribute.If a developer wants to execute it synchronously instead of asynchronously, it needs to move the handler to the
Synchronous
directory + update thesymfony.messenger.routing
configuration.This PR introduces a new way of doing this: auto routing. When enabled, you no longer need to create any routing:
The
MessengerPass
will automatically detect thatCreateProjectHandler
should be invoked with aCreateProjectCommand
and usesfrom_transport: sync
. Therefore, it knows thatCreateProjectCommand
should be routed to thesync
transport. ForDeleteProjectCommand
it seesfrom_transport: async
and creates the routing accordingly.This functionality also works when using
MessageSubscriberInterface
:When auto routing is enabled you can still manually create routes. The
MessengerPass
will simply append anything it detects.