nameko / nameko

Python framework for building microservices
https://www.nameko.io
Apache License 2.0
4.71k stars 470 forks source link

injection with less magic #81

Closed kollhof closed 9 years ago

kollhof commented 10 years ago

The main goal of nameko is to make it easy for developers to write services. A secondary goal is to make it easy for developers to write injection and entry providers to support the first goal.

The current implementation has some shortcomings with respect to the first goal. It just doesn't play well with code analysis tools due to the injection magic.


class FoobarService(object):
    db = db_session(...)

    @rpc
    def shrub(self):
        # should auto-complete in editors that support it
        # should not complain when running flake8 or 
        # similar code analysis tools
        self.db.quer...

# should show the same help as if being inside the shrub() 
# method and calling help(self.db)
help(FoobarService.db)

I propose we remove the factory functions for injeciton providers and adopt a different protocol to declare and manage injections.


class FoobarService(object):
    db = DbSession(...)

    ...

The first step is to declare injections via prototypes. The prototypes are instances of the same type as the objects being injected during a worker life-cycle. This will get rid of the issue we currently have with code analysis tools. Additionally it is more consistent when reading the code.

We now have a new challenge, the life-cycle management of the injection. For each running container we require a provider instance which generates injection instances which are applied to a worker instances of the service class.

We can associate an injection provider with the prototype via it's type, using a decorator.


class DbSessionProvider(InjectionProvider):
    def __init__(self, prototype):
        self.connection_url = prototype.connection_url

    def acquire_injection(self, worker_ctx):
        return DbSession(self.connection_uri, ...)

@injection(DbSessionProvider)
class DbSession(object):
    ...
    def query(self, query, *arg, **kwargs):
        ...

We can now build a map to allow a container to inspect a service class and infer what provider class to use for a particular attribute (injection). When a provider is instantiated it will be given the prototype to allow proper initialization. We could also make it part of an existing or additional life-cycle method. From there on it will behave the same way providers behave now.

A minimal implementation of the injection provider and helper API would look something like:


injection_provider_map = {}

def injection(provider_cls):

    def injection_decorator(cls)
        injection_provider_map[cls] = provider_cls
        return cls

    return injection_decorator

def get_provider_class(prototype):
    return injection_provider_map[type(prototype)]
mattbennett commented 9 years ago

Think we can consider this as done as possible in https://github.com/onefinestay/nameko/pull/187