Services
Table of contents
- Introduction
- Using the methods you get
- Putting a rule in a service
- Answering a form from a route
- Registering a service
- See also
Introduction
A service holds your feature’s business rules. It takes data, applies the rules through a repository and returns results, never an HTTP response. Routes, commands, signal handlers and tests all call the same service, so the rules live in one place.
from splent_framework.services.BaseService import BaseService
from splent_io.splent_feature_notes.repositories import NotesRepository
class NotesService(BaseService):
def __init__(self):
super().__init__(NotesRepository())
def get_by_user(self, user_id):
return self.repository.get_by_column("user_id", user_id)
Using the methods you get
BaseService hands these to its repository.
| Method | What it does |
|---|---|
create(**kwargs) |
Creates a record and commits |
get_by_id(id) |
The record, or None |
get_or_404(id) |
The record, or a 404 |
update(id, **kwargs) |
Sets the fields and commits. None when the id does not exist |
delete(id) |
Deletes and commits. False when the id does not exist |
count() |
How many records there are |
Anything else your service needs goes through self.repository, which also gives you get_by_column and delete_by_column. See Repositories.
Putting a rule in a service
from flask import current_app
class NotesService(BaseService):
def create_note(self, *, title, content):
title = (title or "").strip()
if not title:
raise ValueError("Title is required.")
try:
note = self.create(title=title, content=content)
except Exception:
self.repository.session.rollback()
raise
note_created.send(current_app._get_current_object(), note=note)
return note
The rule holds whoever calls it. On a broken rule the service raises ValueError with a message, and the route decides whether to flash it, return JSON or log it.
A service never returns redirect(...) or a template. A signal handler or a command has no request to answer, so data is the only answer every caller can use.
Answering a form from a route
from splent_framework.utils.form_helpers import form_error, form_success
@notes_bp.route("/notes/<int:note_id>/edit", methods=["GET", "POST"])
@login_required
def edit(note_id):
note = notes_service.get_or_404(note_id)
form = NoteForm(obj=note)
if request.method == "POST":
if form.validate():
notes_service.update(note.id, title=form.title.data)
return form_success("notes.index", "Note updated.")
return form_error("notes/edit.html", form, form.errors, note=note)
return render_template("notes/edit.html", form=form, note=note)
These helpers are HTTP, so they live in the route, never in the service.
| Helper | What it does |
|---|---|
form_success(endpoint, message, category="success", **url_kwargs) |
Flashes message and redirects to url_for(endpoint, **url_kwargs) |
form_error(template, form, errors=None, **context) |
Flashes each field error as "<field>: <message>" under error, and renders template again with form |
form_int(form, key, default=0, low=None, high=None) |
The whole number posted as key, or default when it is missing or not a number, kept between low and high |
Registering a service
Register your services in init_feature, so other features find them, and refine them, by name.
from splent_framework.services.service_locator import register_service
def init_feature(app):
register_service(app, "NotesService", NotesService)
Routes reach them through service_proxy, which builds the registered class on each use.
from splent_framework.services.service_locator import service_proxy
notes_service = service_proxy("NotesService")
Replacing another feature’s service
from splent_framework.refinement import refine_service
def init_feature(app):
refine_service(app, "NotesService", NotesServiceWithTags)
A refinement feature declares the service in [tool.splent.refinement.overrides] services first. An undeclared call, or a second feature refining the same service, stops the start. See Service locator.
register_serviceunder another feature’s name replaces its service with no check. Userefine_service.
See also
- Repositories, the layer your service calls.
- Blueprints, the layer that calls your service.
- Extensibility, for refining another feature’s service.