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:
- Entrypoint fires
- Worker instantiated from service class
- Dependencies injected into worker
- Method executes
- 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 module
s and start running them. You can optionally limit it to specific ServiceClass
s.
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()
If you create your own runner rather than using nameko run, you must also apply the eventlet monkey patch.
See the nameko.cli.run module for an example.