Skip to content

[Messenger] Add Transport attribute to configure message to transport mapping #41179

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
wants to merge 14 commits into from

Conversation

maxim-dovydenok
Copy link
Contributor

@maxim-dovydenok maxim-dovydenok commented May 11, 2021

Q A
Branch? 5.x
Bug fix? no
New feature? yes
Deprecations? no
Tickets Fix #41106
License MIT
Doc PR symfony/symfony-docs#15332

This PR adds Transport attribute to configure Message->Transport mapping. This attribute is read in the SenderLocator class.
Using this attribute it will be possible to configure transport for the message in the message class file instead of messenger.yaml.

@carsonbot
Copy link

Hey!

I think @tyx has recently worked with this code. Maybe they can help review this?

Cheers!

Carsonbot

@maxim-dovydenok
Copy link
Contributor Author

Added some changes:

  • renamed Senders to Transport mapping and made it repeatable
  • made it overwrite file config
  • added reading from parent classes and interfaces
  • and added tests for the cases above

@maxim-dovydenok maxim-dovydenok changed the title [Messenger] Add Senders attribute to configure message to sender mapping [Messenger] Add Transport attribute to configure message to transport mapping May 12, 2021
@ro0NL
Copy link
Contributor

ro0NL commented May 13, 2021

made it overwrite file config

sorry, i meant file config should overwrite attributes. So we have a way to opt-out from whatever the code is telling us.

without attributes AND file config, we could maybe inherit the parent attributes (as we inherit file config). Meaning we also have a way to opt out from the parent attr.

@maxim-dovydenok
Copy link
Contributor Author

maxim-dovydenok commented May 13, 2021

sorry, i meant file config should overwrite attributes. So we have a way to opt-out from whatever the code is telling us.

So lets say we have:

#[Transport('from_attribute')]
class Message implements MessageInterface
// and
routing:
  App/Message/MessageInterface: 'from_interface_config'
  App/Message/Message: 'from_config'

Do you expect it to be sent to from_interface_config, from_config? - attributes are ignored since config is present.

without attributes AND file config, we could maybe inherit the parent attributes (as we inherit file config). Meaning we also have a way to opt out from the parent attr.

Why would we want to opt-out from parent attributes? I mean we don't have an option to opt-out from MessageInterface routing (see example above) currently, at least that I'm aware of, do we?

@ro0NL
Copy link
Contributor

ro0NL commented May 13, 2021

Do you expect it to be sent to from_interface_config, from_config? - attributes are ignored since config is present

Yes.

I see i made a thinking error. Each type either derives from file config, or if unavailable, attributes. Meaning we can opt-out from the parent by also specifying file config for that type.

@nicolas-grekas
Copy link
Member

What's the way to configure a transport right now using a DI tag?
The implementation should reuse that tag instead of harcoding the attribute deep in SenderLocator.

@maxim-dovydenok
Copy link
Contributor Author

What's the way to configure a transport right now using a DI tag?
The implementation should reuse that tag instead of harcoding the attribute deep in SenderLocator.

At the moment we have this code in FrameworkExtension. It reads routing from the config and passes the message-to-senders map to the SendersLocator (where I did changes).

        $messageToSendersMapping = [];
        foreach ($config['routing'] as $message => $messageConfiguration) {
            if ('*' !== $message && !class_exists($message) && !interface_exists($message, false)) {
                throw new LogicException(sprintf('Invalid Messenger routing configuration: class or interface "%s" not found.', $message));
            }

            foreach ($messageConfiguration['senders'] as $sender) {
                if (!isset($senderReferences[$sender])) {
                    throw new LogicException(sprintf('Invalid Messenger routing configuration: the "%s" class is being routed to a sender called "%s". This is not a valid transport or service id.', $message, $sender));
                }
            }

            $messageToSendersMapping[$message] = $messageConfiguration['senders'];
        }

        $sendersServiceLocator = ServiceLocatorTagPass::register($container, $senderReferences);

        $container->getDefinition('messenger.senders_locator')
            ->replaceArgument(0, $messageToSendersMapping)
            ->replaceArgument(1, $sendersServiceLocator)
        ;

There's also code, that uses $config['transports'] to add transports to the container. Each transport has a messenger.receiver tag, but I'm not sure how it can help us to configure routing using attributes.

Copy link
Contributor

@ro0NL ro0NL left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nicolas-grekas this is indeed about routing-messages-to-a-transport

my main motivation here is, to have a single list of things; the messages in code. Or reduce config management if you like :)

@alsar
Copy link

alsar commented Oct 11, 2021

@shiftby Is this still planed for 5.4?

@fabpot fabpot modified the milestones: 5.4, 6.1 Nov 16, 2021
kbond
kbond previously requested changes Mar 15, 2022
Copy link
Member

@kbond kbond left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like this. I think a common pattern is for apps to create Sync/Async marker interfaces to simplify routing rules.

@kbond kbond dismissed their stale review March 17, 2022 18:56

changes made

@kbond
Copy link
Member

kbond commented Mar 17, 2022

@shiftby, I took the liberty of rebasing and cleaning this up - hope we can get this in 6.1.

@ro0NL, @nicolas-grekas, could we get a fresh review?

Copy link
Member

@weaverryan weaverryan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would totally use this :)

$typeSenderAliases = $this->getSendersFromAttributes($type);
}

$senderAliases = array_merge($senderAliases, $typeSenderAliases);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand it correctly, IF even one real route exists for the message (something in $this->sendersMap[$type]), then we do NOT also add the #[Transport] senders? So basically any config would override the attribute?

I think that is... probably the correct behavior. But what about the * catch-all route in messenger.yaml (or an interface)? I'm wondering if someone will have the use-case where they generally use the attribute, but then they decide to, in addition to the transport in each class, route all messages (or all messages for an interface) to a 2nd transport via the routing config in messenger.yaml.

So, I'm conflicted on this point.

$attributeInstance = $attribute->newInstance();

return $attributeInstance->name;
}, $attributes);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's unfortunate to need to do this at runtime (and not compile time). But as messages are not services... or tagged in any way, there is no way for us to collect the attribute information at container build time. So 👍 for this approach.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is indeed unfortunate. Any clever ideas? I understand it depends, but is there a non-negligible performance impact to processing the attributes at runtime?

@kbond
Copy link
Member

kbond commented Mar 18, 2022

I made a little change to allow sub-classes of the Transport attribute. This allows you to create custom, app-specific attributes to attach to your messages:

#[\Attribute(\Attribute::TARGET_CLASS)]
class HighPriority extends Transport
{
    public function __construct()
    {
        parent::__construct('high_priority');
    }
}

#[HighPriority]
class MyMessage {}

@fabpot fabpot modified the milestones: 6.1, 6.2 May 20, 2022
@nicolas-grekas nicolas-grekas modified the milestones: 6.2, 6.3 Nov 5, 2022
@nicolas-grekas nicolas-grekas modified the milestones: 6.3, 6.4 May 23, 2023
@nicolas-grekas nicolas-grekas modified the milestones: 6.4, 7.1 Nov 15, 2023
@xabbuh xabbuh modified the milestones: 7.1, 7.2 May 15, 2024
@pounard
Copy link
Contributor

pounard commented Jun 24, 2024

I opened #57506 which is kind of a duplicate of this issue (sorry for the noise). It seems that there wasn't any activity here for a year or so, what's the status here?

@pounard
Copy link
Contributor

pounard commented Jun 24, 2024

I'm sorry if I hijacked this, but #57507 is ready. This PR is stalled for approximatly a year, and it will cause conflict if merged over the most recent Symfony version, this would mean rewrite pretty much everything.

Should we close this PR?

nicolas-grekas added a commit that referenced this pull request Jun 28, 2024
…sage routing (pounard)

This PR was merged into the 7.2 branch.

Discussion
----------

[Messenger] Introduce `#[AsMessage]` attribute for message routing

| Q             | A
| ------------- | ---
| Branch?       | 7.2
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Issues        | Fix #57506
| License       | MIT

Basic implementation of #57506.

* Adds the `Symfony\Component\Messenger\Attribute\AsMessage` attribute, with the `$transport` parameter which can be `string` or `array`.
* Implement runtime routing in `Symfony\Component\Messenger\Transport\Sender\SendersLocator`.

Rationale:

* Messages are not services, it cannot be computed during container compilation.
* Reflection is very fast, it shouldn't be significant for performances, yet I don't have measured it yet.
* YAML configuration and `Symfony\Component\Messenger\Stamp\TransportNamesStamp` will always override the attribute values, allowing users to change hardcoded routing on a per-environment basis.
* This is the simplest implementation I could think of for discussion.

Links and references:

* #33912 where the discussion started, 5 years ago.
* #49143 closed PR that was doing the same, but at compile time, rejected because the actual doctrine is that messages should never be services.
* #41179 is stilled opened, and awaiting for modifications, but it was written for an earlier version of Symfony and is inactive for a year or so, yet messenger code has evolved since, and this PR will never merge as-is, it requires to be fully rewrote, reason why I opened this new one.

Commits
-------

d652842 feature #57506 [Messenger] introduce AsMessage attribute for message routing
symfony-splitter pushed a commit to symfony/messenger that referenced this pull request Jun 28, 2024
…sage routing (pounard)

This PR was merged into the 7.2 branch.

Discussion
----------

[Messenger] Introduce `#[AsMessage]` attribute for message routing

| Q             | A
| ------------- | ---
| Branch?       | 7.2
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Issues        | Fix #57506
| License       | MIT

Basic implementation of #57506.

* Adds the `Symfony\Component\Messenger\Attribute\AsMessage` attribute, with the `$transport` parameter which can be `string` or `array`.
* Implement runtime routing in `Symfony\Component\Messenger\Transport\Sender\SendersLocator`.

Rationale:

* Messages are not services, it cannot be computed during container compilation.
* Reflection is very fast, it shouldn't be significant for performances, yet I don't have measured it yet.
* YAML configuration and `Symfony\Component\Messenger\Stamp\TransportNamesStamp` will always override the attribute values, allowing users to change hardcoded routing on a per-environment basis.
* This is the simplest implementation I could think of for discussion.

Links and references:

* symfony/symfony#33912 where the discussion started, 5 years ago.
* symfony/symfony#49143 closed PR that was doing the same, but at compile time, rejected because the actual doctrine is that messages should never be services.
* symfony/symfony#41179 is stilled opened, and awaiting for modifications, but it was written for an earlier version of Symfony and is inactive for a year or so, yet messenger code has evolved since, and this PR will never merge as-is, it requires to be fully rewrote, reason why I opened this new one.

Commits
-------

d65284239f feature #57506 [Messenger] introduce AsMessage attribute for message routing
@nicolas-grekas
Copy link
Member

Let us know if #57507 isn't enough. Thanks @maxim-dovydenok for pushing this forward.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Messenger] Message attributes