enthought / enthought/envisage
Awkwardness when advertising more than one service instance of the same type
- Dominant language
- Python
- Stars
- 89
- Forks
- 27
- Avg merge
- 14h 48m
- Merged PRs (30d)
- 7
Description
This issue describes the awkwardness one encounters when two instances of the same type need to be shared across plugins using the service mechanism. (This awkwardness also applies if one must advertise multiple services of the same type even if those services are to be used by the same plugin that advertises them.)
This issue serves to present the problem. This may motivate an alternative design for one plugin to share resources with another plugin.
Starting from this example:
https://github.com/enthought/envisage/blob/28f7958f9db522ce398397a0c09bf6989d8be1a1/examples/legacy/plugins/workbench/Lorenz/acme/lorenz/lorenz_plugin.py#L29-L40
Suppose now we'd like to have two `Lorenz` instances in the application, then one could define two `ServiceOffer` with different factories, with the same protocol, e.g.:
```
lorenz_service_offer_1 = ServiceOffer(
protocol="acme.lorenz.lorenz.Lorenz",
factory=some_factory_1,
)
lorenz_service_offer_2 = ServiceOffer(
protocol="acme.lorenz.lorenz.Lorenz",
factory=some_factory_2,
)
```
The two service instances may have slightly different behaviours, and those differences are important where they are used.
**Ambiguity when obtaining a service from `get_service`**
With that, existing usage of `application.get_service(Lorenz)` would obtain the instance from `some_factory_1` assuming the fact that Python dictionary are ordered and assuming the service offers are contributed in the order of `[lorenz_service_offer_1, lorenz_service_offer_2]`. If the two offers are contributed by separate plugins, then the order depends on the ordering of the plugins too.
#407 is related here
**Existing ways to obtain the second service is fragile or not possible**
To obtain the second one, one will need to use `get_services` with some additional filtering, e.g.:
https://github.com/enthought/envisage/blob/d62af08e75c5555e69c4fc4688393dee737680e8/envisage/tests/test_service_registry.py#L291
That logic depends on `eval`, here:
https://github.com/enthought/envisage/blob/d62af08e75c5555e69c4fc4688393dee737680e8/envisage/service_registry.py#L215
- The usage of `get_services` + `query` may not be possible if the filtering logic depends on information that cannot be retrospectively obtained, i.e. one simply can't provide a query.
- The usage of `eval` is also fragile. It is too easy for the query to be broken for trivial reasons (e.g. attribute name is changed) and the filter finds no match.
- It is hard to maintain these queries: It requires consumers of a service to know a lot about the internal details of the service.
**Workaround is also awkward**
Suppose we cannot compose a `query` for `get_services` or we want to avoid the fragility mentioned above, then one way to workaround this would be to create a subclass of the service, e.g.:
```
class Lozent1(acme.lorenz.lorenz.Lorenz):
pass
class Lozent2(acme.lorenz.lorenz.Lorenz):
pass
```
And then advertise the services:
```
lorenz_service_offer_1 = ServiceOffer(
protocol=Lozent1,
factory=some_factory_1,
)
lorenz_service_offer_2 = ServiceOffer(
protocol=Lozent2,
factory=some_factory_2,
)
```
Part of this is imposed by the fact that protocol must be either a type or a string that can be used for importing a type.
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.