Dependencies between dependency providers?


#1

Is it possible fro one dependency to provider to depend in another dependency provider? Similar to what can be achieved in Pytest with fixtures (fixtures can use other fixtures).

In my case I have two dependencies:

  1. Provides a Cassandra connection with a default keyspace.
  2. Uses the Cassandra connection to run some initialization during the ‘start’ method, and provides a function that is used in my application. It would be nice to be able to use the dependency provided by #1.

I could create another dependency that uses either composition or inheritance, but the ‘get_dependency’ method still needs to return one or the other. What is a common and nameko-ish way of solving cases like these?


#2

Disclaimer: Code shown here is not tested.

Figured out a half-solution after reading this:

Basically, you can access the other extensions at the cost of coupling some of them. It is useful for de-coupling the code, but not their functional dependency on each other. This is how to do it:

class DependencyA(DependencyProvider):
    def setup(self):
        ...

    def get_dependency(self, worker_ctx):
        ...

class DepencyB(DependencyProvider):
    def setup(self):
        ...

    def start(self):
        # at this point all other extensions (entrypoints / dependencies) already went
        # through the 'setup' phase.

        # creates a map from attribute name (defined in the main service class) to the dependency
        # provider.
        dependency_providers = {dep.attr_name: dep for dep in self.container.dependencies}

        # get dependency by attribute name
        dependency_a = dependency_providers['dependency_a'].get_dependency(None)

        # get dependency by simple type checking
        results = list(filter(lambda d: isinstance(d, (DependencyA,)), dependency_providers.values()))
        if len(results) >= 1:
            dependency_a = results[0].get_dependency(None)

Definitely not simple, but helps to mitigate spaghetti code a little (when one dependency is doing too much).


#3

I got half-way through a reply to your original message and then got distracted, sorry. Looks like you’ve worked something out. For reference, here is what I was going to say:


The short answer is no, you can’t have one DependencyProvider depend on another. That’s because the dependency injection step is specifically about injecting the result of get_dependency into a service worker. The injection mechanism only works from DP to service, not DP to DP.

A DependencyProvider can have nested Extensions or SharedExtensions though. A good example of this is the HttpRequestHandler (@http) entrypoint, which has a WebServer SharedExtension. In this arrangement there’s a single webserver listening on the port, handling requests for all the @http entrypoints.

I think I would use straight-up inheritance to solve your problem though. Is there a problem with using one DependencyProvider to establishes connections to Cassandra, and another that subclasses it to run the initialisation and provides the function to the service?


I don’t have enough information to comment on your particular use-case, but I generally discourage coupling extensions by name. It is often a sign of either overzealous code re-use, or poor separation of concerns.

By way of example, imagine a service that stores metrics and application data on the same physical database. It’s common to want to reuse the DependencyProvider from one for the other, whereas IMO it’d be better to use two explicitly separate extensions even if that involved some code duplication.


#4

Hi @mattbennett,

Thanks for your response. I think you are right about trying to re-use code too much. I think the best approach is to inherit classes or just create a new extension (if code duplication is not that much).

Best regards.