Skip to content

[DI] add ReverseContainer: a locator that turns services back to their ids #30334

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

Merged

Conversation

nicolas-grekas
Copy link
Member

@nicolas-grekas nicolas-grekas commented Feb 21, 2019

Q A
Branch? master
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets -
License MIT
Doc PR -

This PR introduces a ReverseContainer, which is a class you can type hint for to get it as a service.

When you have a ReverseContainer at hand, you can then use it to know the service id of an object (if the object is not found, null is returned):
$id = $reverseContainer->getId($someObject);

You can also call $reverseContainer->getService($id); and get the service in return.

To be reversible, a service must either be public or be tagged with container.reversible.

I'm using this feature to serialize service references in a message, then send them through a Messenger bus, allowing the handler on the other side to use that referenced service to process the message. More specifically, my use case is sending messages for early cache expiration events through a bus and have a worker compute the soon-to-expire value in the background. The reversible services are the computation callbacks and the cache pools I need to compute the value for.

Copy link
Contributor

@linaori linaori left a comment

Choose a reason for hiding this comment

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

I'm always amazed with your solutions 👍 !

@nicolas-grekas nicolas-grekas force-pushed the di-referenceable-container branch from 780a6df to fe4d7de Compare February 22, 2019 08:36
@nicolas-grekas nicolas-grekas force-pushed the di-referenceable-container branch 3 times, most recently from f8a22fc to b6eefca Compare February 23, 2019 14:24
@nicolas-grekas
Copy link
Member Author

ping @symfony/deciders

*
* @throws ServiceNotFoundException When the service is not reversible
*/
public function getService(string $id)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not let people use a normal service locator for this?

Copy link
Member Author

@nicolas-grekas nicolas-grekas Mar 9, 2019

Choose a reason for hiding this comment

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

Because it's not a normal service locator: you can get here only the ids you can fetch with the other method before. It wouldn't make sense to provide a standard locator interface here Having a different interface highlights this is not the same kind as a regular locator.

Copy link
Contributor

Choose a reason for hiding this comment

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

isn't it the same as having a service locator for services tagged with container.reversible that you can easily do manually?

Copy link
Member Author

Choose a reason for hiding this comment

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

those, + public services

Copy link
Contributor

Choose a reason for hiding this comment

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

So why does it need the method then?

Copy link
Member Author

@nicolas-grekas nicolas-grekas Mar 9, 2019

Choose a reason for hiding this comment

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

Because an instance of this class is a single consistent scope of services you can reverse.
Providing only object->id mapping here, and relying on another object to do the reverse would mean the two objects would be bound by some external convention that cannot be enforced by any contracts. Having both methods in this class is what provides the needed guarantee, from the abstraction pov.

@nicolas-grekas nicolas-grekas force-pushed the di-referenceable-container branch 2 times, most recently from 9046dbb to 6533251 Compare March 9, 2019 10:16
@nicolas-grekas nicolas-grekas force-pushed the di-referenceable-container branch from c8dfc57 to ac1e429 Compare March 14, 2019 16:23
@nicolas-grekas nicolas-grekas merged commit ac1e429 into symfony:master Mar 15, 2019
nicolas-grekas added a commit that referenced this pull request Mar 15, 2019
…es back to their ids (nicolas-grekas)

This PR was merged into the 4.3-dev branch.

Discussion
----------

[DI] add ReverseContainer: a locator that turns services back to their ids

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT
| Doc PR        | -

This PR introduces a `ReverseContainer`, which is a class you can type hint for to get it as a service.

When you have a `ReverseContainer` at hand, you can then use it to know the service id of an object (if the object is not found, `null` is returned):
`$id = $reverseContainer->getId($someObject);`

You can also call `$reverseContainer->getService($id);` and get the service in return.

To be reversible, a service must either be public or be tagged with `container.reversible`.

I'm using this feature to serialize service references in a message, then send them through a Messenger bus, allowing the handler on the other side to use that referenced service to process the message. More specifically, my use case is sending messages for early cache expiration events through a bus and have a worker compute the soon-to-expire value in the background. The reversible services are the computation callbacks and the cache pools I need to compute the value for.

Commits
-------

ac1e429 [DI] add ReverseContainer: a locator that turns services back to their ids
@nicolas-grekas nicolas-grekas deleted the di-referenceable-container branch March 15, 2019 13:41
@nicolas-grekas nicolas-grekas modified the milestones: next, 4.3 Apr 30, 2019
@fabpot fabpot mentioned this pull request May 9, 2019
fabpot added a commit that referenced this pull request Sep 12, 2020
…ing cached values in a worker (nicolas-grekas)

This PR was merged into the 5.2-dev branch.

Discussion
----------

[Cache] add integration with Messenger to allow computing cached values in a worker

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT
| Doc PR        | -

~~This PR needs and for now embeds #30334. See 2nd commit.~~

Using the new `CacheInterface` enables probabilistic early expiration by default. This means that from time to time, items are elected as early-expired while they are still fresh ([see Wikipedia](https://en.wikipedia.org/wiki/Cache_stampede#Probabilistic_early_expiration) for details).

This PR adds a new `early_expiration_message_bus` option when configuring cache pools. When this option is set, cache pools are configured to send those early-expired items on a Messenger bus, then serve the current value immediately, while the updated value is computed in a worker in the background.

`CacheInterface::get($key, $callback)` accepts any callable, but sending any callable on a bus is not possible (e.g. a `Closure` cannot be serialized). To bypass this constraint, this feature works only with callables in the form `[$service, 'publicMethod']`, where `$service` is any public or [reversible service](#30334).

This constraint is a serious one: this $service must compute a value when knowing only its key. This means keys should embed enough information for this to happen. I think that's not that hard - and we may find ways to provide additional context in the future.

At least the goal is worth it: in theory, this strategy allows achieving a 100% hit ratio even when invalidation-by-expiration is used.

There are two things one needs to do to enable this behavior:

1. bind a message bus to a cache pool:
```yaml
framework:
    cache:
        pools:
            test.cache:
                early_expiration_message_bus: messenger.default_bus
```

2. route EarlyExpirationMessage to a transport:
```yaml
framework:
    messenger:
        routing:
            'Symfony\Component\Cache\Messenger\EarlyExpirationMessage': amqp
```

Commits
-------

6c0911f [Cache] add integration with Messenger to allow computing cached values in a worker
symfony-splitter pushed a commit to symfony/cache that referenced this pull request Sep 12, 2020
…ing cached values in a worker (nicolas-grekas)

This PR was merged into the 5.2-dev branch.

Discussion
----------

[Cache] add integration with Messenger to allow computing cached values in a worker

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT
| Doc PR        | -

~~This PR needs and for now embeds #30334. See 2nd commit.~~

Using the new `CacheInterface` enables probabilistic early expiration by default. This means that from time to time, items are elected as early-expired while they are still fresh ([see Wikipedia](https://en.wikipedia.org/wiki/Cache_stampede#Probabilistic_early_expiration) for details).

This PR adds a new `early_expiration_message_bus` option when configuring cache pools. When this option is set, cache pools are configured to send those early-expired items on a Messenger bus, then serve the current value immediately, while the updated value is computed in a worker in the background.

`CacheInterface::get($key, $callback)` accepts any callable, but sending any callable on a bus is not possible (e.g. a `Closure` cannot be serialized). To bypass this constraint, this feature works only with callables in the form `[$service, 'publicMethod']`, where `$service` is any public or [reversible service](symfony/symfony#30334).

This constraint is a serious one: this $service must compute a value when knowing only its key. This means keys should embed enough information for this to happen. I think that's not that hard - and we may find ways to provide additional context in the future.

At least the goal is worth it: in theory, this strategy allows achieving a 100% hit ratio even when invalidation-by-expiration is used.

There are two things one needs to do to enable this behavior:

1. bind a message bus to a cache pool:
```yaml
framework:
    cache:
        pools:
            test.cache:
                early_expiration_message_bus: messenger.default_bus
```

2. route EarlyExpirationMessage to a transport:
```yaml
framework:
    messenger:
        routing:
            'Symfony\Component\Cache\Messenger\EarlyExpirationMessage': amqp
```

Commits
-------

6c0911f58c [Cache] add integration with Messenger to allow computing cached values in a worker
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.

8 participants