Skip to content
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

Awkwardness when advertising more than one service instance of the same type #415

Open
kitchoi opened this issue May 18, 2021 · 0 comments

Comments

@kitchoi
Copy link
Contributor

kitchoi commented May 18, 2021

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:

# Service offers.
service_offers = List(contributes_to=SERVICE_OFFERS)
def _service_offers_default(self):
""" Trait initializer. """
lorenz_service_offer = ServiceOffer(
protocol="acme.lorenz.lorenz.Lorenz",
factory="acme.lorenz.lorenz.Lorenz",
)
return [lorenz_service_offer]

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.:

services = self.service_registry.get_services(IFoo, "price >= 100")

That logic depends on eval, here:
result = eval(query, namespace)

  • 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.

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

No branches or pull requests

1 participant