Key Concepts

This section introduces Nameko’s central concepts.

Anatomy of a Service

A Nameko service is just a Python class. The class encapsulates the application logic in its methods and declares any dependencies as attributes.

Methods are exposed to the outside world with entrypoint decorators.

from nameko.rpc import rpc, RpcProxy

class Service:
    name = "service"

    # we depend on the RPC interface of "another_service"
    other_rpc = RpcProxy("another_service")

    @rpc  # `method` is exposed over RPC
    def method(self):
        # application logic goes here
        pass

Entrypoints

Entrypoints are gateways into the service methods they decorate. They normally monitor an external entity, for example a message queue. On a relevant event, the entrypoint may “fire” and the decorated method would be executed by a service worker.

Dependencies

Most services depend on something other than themselves. Nameko encourages these things to be implemented as dependencies.

A dependency is an opportunity to hide code that isn’t part of the core service logic. The dependency’s interface to the service should be as simple as possible.

Declaring dependencies in your service is a good idea for lots of reasons, and you should think of them as the gateway between service code and everything else. That includes other services, external APIs, and even databases.

Workers

Workers are created when an entrypoint fires. A worker is just an instance of the service class, but with the dependency declarations replaced with instances of those dependencies (see dependency injection).

Note that a worker only lives for the execution of one method – services are stateless from one call to the next, which encourages the use of dependencies.

A service can run multiple workers at the same time, up to a user-defined limit. See concurrency for details.

Dependency Injection

Adding a dependency to a service class is declarative. That is, the attribute on the class is a declaration, rather than the interface that workers can actually use.

The class attribute is a DependencyProvider. It is responsible for providing an object that is injected into service workers.

Dependency providers implement a get_dependency() method, the result of which is injected into a newly created worker.

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.other_rpc = worker.other_rpc.get_dependency()
worker.method()
del worker

Dependency providers live for the duration of the service, whereas the injected dependency can be unique to each worker.

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

Extensions

All entrypoints and dependency providers are implemented as “extensions”. We refer to them this way because they’re outside of service code but are not required by all services (for example, a purely AMQP-exposed service won’t use the HTTP entrypoints).

Nameko has a number of built-in extensions, some are provided by the community and you can write your own.

Running Services

All that’s required to run a service is the service class and any relevant configuration. The easiest way to run one or multiple services is with the Nameko CLI:

$ nameko run module:[ServiceClass]

This command will discover Nameko services in the given modules and start running them. You can optionally limit it to specific ServiceClasss.

Service Containers

Each service class is delegated to a ServiceContainer. The container encapsulates all the functionality required to run a service, and also encloses any extensions on the service class.

Using the ServiceContainer to run a single service:

from nameko.containers import ServiceContainer

class Service:
    name = "service"

# create a container
container = ServiceContainer(Service, config={})

# ``container.extensions`` exposes all extensions used by the service
service_extensions = list(container.extensions)

# start service
container.start()

# stop service
container.stop()

Service Runner

ServiceRunner is a thin wrapper around multiple containers, exposing methods for starting and stopping all the wrapped containers simultaneously. This is what nameko run uses internally, and it can also be constructed programmatically:

from nameko.runners import ServiceRunner
from nameko.testing.utils import get_container

class ServiceA:
    name = "service_a"

class ServiceB:
    name = "service_b"

# create a runner for ServiceA and ServiceB
runner = ServiceRunner(config={})
runner.add_service(ServiceA)
runner.add_service(ServiceB)

# ``get_container`` will return the container for a particular service
container_a = get_container(runner, ServiceA)

# start both services
runner.start()

# stop both services
runner.stop()