Light python implementation of service locator pattern based on java CDI and ServiceLoader api.
The service locator pattern is considered by some authors as an anti pattern, which recommend using DI (Dependency Injection), even so Martin Fowler recommends not demonizing it, and use it when it is convenient.
- Define the @ServiceProvider decorator to configure the service locator registry population by specifying a marker interface, an string qualifier and a scope.
- Define the ServiceLocator class, which is a singleton that implements the service locator registry. The ServiceLocator allows register and lookup implementations of a service.
- Define the ServiceLookup class, which is a facade of the ServiceLocator lookup method.
- Python version: >= 3.4
- setuptools
- setuptools-markdown
git clone https://github.com/alexescalonafernandez/service-locator.git
cd service-locator
python setup.py sdist
cd dist
pip install service-locator-1.0.tar.gz
# example_repository.py
from service_locator import qualifiers
from service_locator.decorators import ServiceProvider
@ServiceProvider(qualifiers.Repository, 'ExampleRepository')
class ExampleRepository:
def do_action(self):
print('do repository action')
# example_controller.py
from service_locator import qualifiers
from service_locator.decorators import ServiceProvider
from service_locator.lookup import ServiceLookup
@ServiceProvider(qualifiers.Controller, 'ExampleController')
class ExampleController:
def __init__(self, repository=ServiceLookup.proxy(qualifiers.Repository, 'ExampleRepository')):
self.repository = repository
def do_action(self):
self.repository.do_action()
# main.py
from service_locator import _command
from service_locator.lookup import ServiceLookup
from service_locator import qualifiers
# set here the main module path for scanning service providers
module_path = 'main module path'
# generate _services.py files with providers configuration
_command.generate_providers_configuration(module_path)
# import _services.py dynamically
_command.populate_service_locator_registry(module_path)
controller = ServiceLookup.lookup(qualifiers.Controller, 'ExampleController')
controller.do_action()
The proxy method differs from the lookup in that lookup requests an instance from the provider immediately, instead proxy returns a dynamic proxy from the instance so that the instance is only created when any property or method of the instance representing by the proxy is invoked.
It is recommended to use the proxy method in all dependency injections at the parameter level in all the scripts, which uses the @ServiceProvider decorator.
To understand the above, let's analyze what would happen if the controller class is loaded before the repository one. The controller would get a null instance because the service locator registry does not have any provider for the repository.
This means that the order in which the _services.py scripts are loaded is important. The proxy method allows the service locator registry to work correctly regardless of the order in which the scripts are loaded. Otherwise, it would be necessary to implement a topological sort of the scripts to be loaded.