2

I am busy programming a microservice backend in Python using Nameko. While searching for a good dependency injection package I came a across Injector. I really liked it and wanted to use it together with Nameko. Then I noticed a small problem: Nameko instantiates the workers and doesn't work with dependency injection packages out of the box. After trying to get it to work using the documentation I stumbled upon the package nameko-injector. I liked the concept and tried to implement it but i get the error:

Parameter 'bindings' unfilled

Using the example code from the git repository (shown below) the problem occurred at the initialization of the NamekoInjector class.

The microservice worker class:

from nameko.rpc import rpc
from services.order_service import OrderService
from nameko_injector.core import NamekoInjector

INJECTOR = NamekoInjector()


@INJECTOR.decorate_service
class OrderWorker:

    # Mandatory field for service discovery
    name = "order_worker"

    def __init__(self, service: OrderService):
        self.service = service

    @rpc
    def get_orders(self):
        return self.service.orders()

The OrderService class:

from models.order.order import Order


class OrderService(object):

    def __init__(self):
        self.orders = [Order(1), Order(2)]

    def get_users(self):
        return self.orders

The Order class:

class Order:
    def __init__(self, orderid):
        self.orderid = orderid

    def __str__(self):
        return str(self.orderid)

When looking in the NamekoInjector class I couldn't find out what the binding exactly does and when it's used. In the first place I don't even need it but when I delete the binding fields and other usages in the NamekoInjector class, it still won't work. Can anybody please help me? Thanks!

Job Deleij
  • 21
  • 2

1 Answers1

0

I realise it's late but maybe still relevant. You were quite close to the solution.

The missing parts:

  • configure function that is passed to the NamekoInjector. It tells the library how to create the dependencies, their scope etc.
  • The dependencies must be specified on the entry point, not in the worker class initialiser.

I modified a bit your code and put it in one module but you are free to organise the code as you wish. I did it for simplicity.

The code below should work as-is. I also included bits that helped me run the project.

import injector
from nameko.rpc import rpc
from nameko_injector.core import NamekoInjector

class Order:
    def __init__(self, orderid):
        self.orderid = orderid

    def __str__(self):
        return str(self.orderid)

class OrderService(object):

    def __init__(self):
        self.orders = [Order(1), Order(2)]

    def get_orders(self):
        return self.orders
    
class Calculator:
    # class to demonstrate transitive dependency
    def __init__(self, randomiser):
        self.__randomiser = randomiser

    def calculate(self):
        return self.__randomiser()

# I usually keep the providers in the same place with the provided
@injector.provider
def provide_calculator() -> Calculator:
    # simple function that initialises one dependency
    return Calculator(randomiser=lambda: 42)

    
class ComplexInitService:
    """Service with own dependencies."""
    # the service to demonstrate providers.
    
    def __init__(self, calculator: Calculator):
        self.__calculator = calculator
  
    def calculate(self):
        return self.__calculator.calculate()

@injector.provider
def provide_complex_service(calculator: Calculator) -> ComplexInitService:
    return ComplexInitService(calculator)

def configure(binder):
    # function that tells the framework how to create a dependency based on the requested type
    # in the RPC/HTTP endpoint
    binder.bind(Calculator, to=provide_calculator)
    binder.bind(ComplexInitService, to=provide_calculator)
    
INJECTOR = NamekoInjector(configure)

@INJECTOR.decorate_service
class OrderWorker:

    # Mandatory field for service discovery
    name = "order_worker"

    @rpc
    def get_orders(self, service: OrderService):
        return [order.orderid for order in service.get_orders()]
    
    @rpc
    def calcualte_complex_stuff(self, service: ComplexInitService):
        return service.calculate()

I used the following snippets to init the project

rm pyproject.toml poetry.lock
poetry init \
--name nameko-injector-demo \
--no-interaction \
--dependency 'nameko-injector:1.1.2'
# poetry install

Nameko config example

AMQP_URI: 'amqps://user:password@vhots.rmq.cloudamqp.com/vhost'
rpc_exchange: 'nameko-rpc'
max_workers: 10
parent_calls_tracked: 10

Run the service

poetry run nameko run --config config.yml service:OrderWorker

Test with nameko shell

poetry run nameko shell --config config.yml
Nameko Python 3.8.9 (default, May 13 2021, 00:13:06)
[Clang 7.1.0 (tags/RELEASE_710/final)] shell on darwin
Broker: amqps://user:password@crow.rmq.cloudamqp.com/vhost (from --config)
>>> n.rpc.order_worker.get_orders()
[1, 2]
>>> n.rpc.order_worker.calcualte_complex_stuff()
42
Dharman
  • 30,962
  • 25
  • 85
  • 135
signalpillar
  • 916
  • 7
  • 3