Skip to content

Key Concepts

Anatomy of a Service

A Nameko service is written as a Python class. The class encapsulates the application logic in its methods.

Methods can be exposed by decorating them with an Entrypoint extension. The Entrypoint defines the interface through which the method can be called, and also implements the mechanism.

To interact with anything other than itself, a service should use a Dependency Provider extension. The Dependency Provider is an abstraction around an external entity – the interface of another service, a data store, or an API.

Entrypoints

Entrypoints decorate service methods and define how that method can be invoked.

class Service:
    name = "service"

    @http("GET", "/foo")
    def serve_foo(self, request):
        return 200, "OK"

This @http entrypoint listens for GET requests to the /foo resource, and invokes serve_foo when one is made –– this is referred to as the entrypoint “firing”.

When an entrypoint fires, Nameko creates a new instance of the service class (called a “worker”) and calls the decorated service method on the worker. Since every worker is a fresh instantiation of the service class, they are inherently stateless.

Dependency Providers

Almost every microservice will depend on something other than itself, like a database or some other microservices (otherwise it’d be a monolith). Dependency Providers are how services should do this.

Since a Nameko service is just a Python class you can write arbitrary Python to do whatever you like in your service methods, but using a Dependency Provider is a best practise that makes code more testable and easier to understand.

Like Entrypoints, a Dependency Provider is instantiated when a service starts, and that instance survives as long as the service is running. Contrast this to workers, which are created and destroyed every time an entrypoint fires.

The Dependency Provider’s job is to provide an object (often called the dependency, though usually a client that lets you access that dependency) which is injected into workers when they’re created. The term is borrowed from the Dependency Injection pattern.

A Dependency Provider must implement a get_dependency method, which returns the object to be injected. This can return a reference to a persistent object or construct a new object every time it’s called, depending on the use-case. Importantly, the returned object will be accessed by multiple workers concurrently, so should be thread-safe.

A Dependency Provider is a great place to hide complexity. Only the object returned from get_dependency is available to the worker, so you can nicely separate the concerns of the dependency interface from the code required to provide that dependency.

Workers

Workers are created when an entrypoint fires. A worker is an instance of the service class, but with the dependency providers replaced with dependency objects they providing, as described in (dependency_providers)[#dependency-providers] above. This process is also referred to as dependency injection.

A worker only exists for the execution of one method –– services are stateless from one call to the next.

The lifecycle of a worker is:

  1. Entrypoint fires
  2. Worker instantiated from service class
  3. Dependencies injected into worker
  4. Method executes
  5. Worker is destroyed

In pseudocode this looks like:

worker = Service()
worker.dependency_provider = worker.dependency_provider.get_dependency()
worker.method()
del worker

Note

Don’t give your service an __init__ method

It’s a common misconception that Nameko services are instantiated once at startup. A new instance of the service class is created for every worker, so the constructor is called every time an entrypoint fires.

If you need to set up some initial state in a service, the setup method in a Dependency Provider is the place to do it.

Concurrency

Nameko is built on top of the eventlet library, which provides concurrency via “greenthreads”. The concurrency model is co-routines with implicit yielding.

Implicit yielding relies on monkey patching the standard library, to trigger a yield when a thread waits on I/O. If you run services with nameko run on the command line, Nameko will apply the monkey patch for you.

Each worker executes in its own greenthread. The maximum number of concurrent workers can be tweaked based on the amount of time each worker will spend waiting on I/O.

Workers are stateless so are inherently thread safe, but dependencies should ensure they are unique per worker or otherwise safe to be accessed concurrently by multiple workers.

Note that many C-extensions that are using sockets and that would normally be considered thread-safe may not work with greenthreads. Among them are librabbitmq, MySQLdb and others.