Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[Messenger] Why are you forced to specify a "default bus"? #32030

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

Closed
Nyholm opened this issue Jun 13, 2019 · 12 comments
Closed

[Messenger] Why are you forced to specify a "default bus"? #32030

Nyholm opened this issue Jun 13, 2019 · 12 comments

Comments

@Nyholm
Copy link
Member

Nyholm commented Jun 13, 2019

Description
When configure your busses it could look like this:

framework:
    messenger:
        default_bus: messenger.bus.command
        buses:
            messenger.bus.command:
                middleware:
                    - App\Message\Middleware\SpecialLoggingMiddleware
                    - validation
                    - doctrine_transaction

            messenger.bus.event:
                default_middleware: allow_no_handlers
                middleware:
                    - validation

            messenger.bus.query:
                middleware:
                    - validation

Since we are using multiple busses we are forced to define default_bus. Why?
I understand it is for autowire but what if I dont want it to autowire?

The issue is the following:

I define my services like

services:
    _defaults:
        autowire: true
        autoconfigure: true
        bind:
            $eventBus: '@messenger.bus.event'
            $commandBus: '@messenger.bus.command'

So if I create two PHP classes like this:

class Foo{
    private $eventBus;
    public function __construct(MessageBusInterface $eventBus) {
        $this->eventBus = $eventBus;
    }
}

class Bar{
    private $queryBus;
    public function __construct(MessageBusInterface $queryBus) {
        $this->queryBus = $queryBus;
    }
}

Foo will get an instance of my messenger.bus.event and Bar will get an instance of messenger.bus.command. You would expect Bar to get messenger.bus.query, but I've forgotten to add that under services._default.bind and autowire "helps" me to get the configured default_bus.

I would much rather not define a default_bus and then get an exception since it cannot autowire MessageBusInterface $queryBus.


Im sure there is a reason why we need a default_bus but I cannot see it.

@nicolas-grekas
Copy link
Member

Can you try using named autowiring aliases?
ie name you buses query.bus + event.bus and have it them work without any binding using $queryBus/$eventBus as argument name (as you do already)?

@Nyholm
Copy link
Member Author

Nyholm commented Jun 13, 2019

Yeah, that will work. But if I name a variable $foobarBus, then I will still the the messenger.bus.command instead of an exception. The exception is preferred in this case.

@nicolas-grekas
Copy link
Member

nicolas-grekas commented Jun 13, 2019

Oh, I agree! I even thought it was the case when I first contributed around this :)

@Tobion
Copy link
Contributor

Tobion commented Jun 14, 2019

The default bus requirement is currently enforced mainly because of

  1. if (!$this->container->has('messenger.default_bus')) {
    $message = class_exists(Envelope::class) ? 'You need to define the "messenger.default_bus" configuration option.' : 'Try running "composer require symfony/messenger".';
    throw new \LogicException('The message bus is not enabled in your application. '.$message);
    }

  2. As fallback for the RoutableMessageBus

Not sure what the best solution for 1. would be. Require people to specify the bus in this method?
For 2. we could solve it by having a bus option on the transport instead as I suggest in #32049. So consuming messages that are also sent by SF, will work automatically because of the BusNameStamp. Consuming third-party messages requires the bus option on the transport. No need for an implicit fallback.

@Tobion
Copy link
Contributor

Tobion commented Jun 14, 2019

Ref. #31662

@weaverryan
Copy link
Member

The downside to making default_bus optional is that normal, non-named autowiring will work with 1 bus, but then when you add a second, it'll start to fail... which is the same as now, but the error message will be much more obtuse: you'll see an autowiring failed message instead of a concrete message that you're missing a config key.

So, if we make it optional, I think that should be considered.

@Tobion
Copy link
Contributor

Tobion commented Jul 13, 2019

I was thinking about how to fix this and also the third point in #32049:
The main problem is the RoutableMessageBus that currently relies on the BusNameStamp to be aded to envelopes and also requires a default bus.
The bus-transport-relation is an n-m-relation. In theory several buses can send to the same transport (#30631) and the same message can be sent to several transports.
The second part would be easily solved with a bus option on the transport so that messages received from this transport go automatically to the configured bus. If you have only a single bus, it get's used directly. If you have more than one bus, you need to specify the bus config on the transport.
The first part is what the BusNameStamp and RoutableMessageBus was added for A simple bus option on the transport or --bus worker option does not suffice. But the BusNameStamp also has problems. Message handling is async. Adding the bus name to the envelope does not account for this at all. If you rename or redefine your buses, all messages in the queues can't be handled because the BusNameStamp has an outdated bus name. This can easily happen esp. with delayed messages and retried messages or even messages in the failure transport.
So the simingly correct solution would be to reverse the message routing. This shouldn't be hard but requires to make the message routing bus-specific. Currently the message routing is global. If it's per bus we could easily find the bus that a message received from a transport belongs to.

@weaverryan
Copy link
Member

Hmm, this very well may make sense.

If you have one bus only, you would not need this config. If you had multiple buses, then each transport would need a bus to be defined. And there would be no validation - I.e. you could in theory dispatch to bus B and transportFoo, then configure transportFoo to dispatch back to busA, correct?

I think that makes enough sense. But, strictly speaking for this issue, wasn’t the “default” bus (#31288) added just as a nice “fallback” if the stamp were missing? We could just remove that (or deprecate it). We need an argument behind just this issue to change the bus name stamp logic I think. I think your motivation is about making custom, non-messenger-originates messages easier to work with? We could do that by adding your option to each transport as an “override”. I mean, if that option is set, use that bus. Else, use the BusNameStamp. Else, an exception (remove the default bus from routable).

I’m debating between this option + the stamp (“easier” as normal users never new to think about this option) vs only the option and remove the stamp (a bit more work for users with multiple buses, but more explicit).

Cheers!

@jaikdean
Copy link

But if I name a variable $foobarBus, then I will still the the messenger.bus.command instead of an exception. The exception is preferred in this case.

I thoroughly agree with this. I'm erring towards not using the framework configuration and just defining the messengers manually through services.yaml, primarily for this reason.

@Tobion
Copy link
Contributor

Tobion commented Nov 6, 2019

I’m debating between this option + the stamp (“easier” as normal users never new to think about this option) vs only the option and remove the stamp (a bit more work for users with multiple buses, but more explicit).

I think the BusNameStamp is bad because it does not respect that message handling is async. If you dispatch a message with bus A and delay (or it goes into retry), then rename the bus and redeploy, the message cannot be consumed suddenly because bus A does not exist anymore.

This is why explicitly setting the bus on the transport seems like the best solution.

@carsonbot
Copy link

Thank you for this issue.
There has not been a lot of activity here for a while. Has this been resolved?

@Nyholm
Copy link
Member Author

Nyholm commented Feb 19, 2021

Yes, I think it has been. I suggest no changes.

@Nyholm Nyholm closed this as completed Feb 19, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants