Service locator
Table of contents
Introduction
Features register replaceable service classes, and routes look up the current implementation at request time, after every feature, refiners included, has loaded. That is what lets a refiner swap a service without the base knowing.
# splent_feature_notes/__init__.py
from splent_framework.services.service_locator import register_service
from .services import NotesService
def init_feature(app):
register_service(app, "NotesService", NotesService)
Using a service in your routes
# splent_feature_notes/routes.py
from splent_framework.services.service_locator import service_proxy
notes_service = service_proxy("NotesService")
@notes_bp.route("/notes/", methods=["GET"])
@login_required
def index():
notes = notes_service.get_by_user(current_user.id)
...
service_proxy resolves the registered class at request time, the way Flask resolves current_app.
Never import the service class in routes (
from .services import NotesService). The import creates the instance when the module loads, before refiners register their overrides.
Overriding a service from a refiner
# splent_feature_notes_tags/__init__.py
from splent_framework.refinement import refine_service
from .services import NotesServiceWithTags
def init_feature(app):
refine_service(app, "NotesService", NotesServiceWithTags)
[tool.splent.refinement.overrides]
services = [{ target = "NotesService", replacement = "NotesServiceWithTags" }]
refine_service builds a class that inherits from both, so super() works, and service_proxy("NotesService") returns it. An undeclared call stops startup with FeatureError, and so does a second refiner of the same service. A call from outside any feature, such as a test, is not checked.
| Replacement class | Result |
|---|---|
A plain class without __init__ |
Merged with the base. Its methods win, and super() reaches the base |
| A subclass of the base service | Registered as it is |
A class with its own __init__ that does not subclass the base |
Refused at startup, since its super().__init__(...) would reach the base’s. Drop the __init__, or subclass the base |
A refiner always uses
refine_service.register_serviceunder another feature’s name bypasses every check and silently wins.
See also
- Services, the service layer
- Refinement, every kind of override