Services
Services contain business logic. They receive data, apply rules, and return results — never HTTP responses.
Table of contents
What they do
A service wraps a repository and adds domain-specific methods. Routes call services; services call repositories. This keeps business rules in one place, testable and reusable.
Creating a service
# splent_feature_notes/services.py
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)
The base class gives you CRUD for free:
| Method | What it does |
|---|---|
create(**kwargs) |
Create and persist a record |
get_by_id(id) |
Find by primary key |
get_or_404(id) |
Find or abort 404 |
update(id, **kwargs) |
Update fields and commit |
delete(id) |
Delete by primary key |
count() |
Total records |
Real example: authentication
# splent_feature_auth/services.py
class AuthenticationService(BaseService):
def __init__(self):
super().__init__(UserRepository())
def login(self, email, password, remember=True):
user = self.repository.get_by_email(email)
if user is not None and user.check_password(password):
login_user(user, remember=remember)
return True
return False
def is_email_available(self, email):
return self.repository.get_by_email(email) is None
def create_user(self, **kwargs):
user = self.create(email=kwargs["email"], password=kwargs["password"], active=False)
user_registered.send(current_app._get_current_object(), user=user, **kwargs)
return user
Real example: profile update
# splent_feature_profile/services.py
class UserProfileService(BaseService):
def __init__(self):
super().__init__(UserProfileRepository())
def get_by_user_id(self, user_id):
results = self.repository.get_by_column("user_id", user_id)
return results[0] if results else None
def update_profile(self, user_profile_id, form):
if form.validate():
return self.update(user_profile_id, **form.data), None
return None, form.errors
Why services don’t handle HTTP
Services need to work from anywhere:
- Routes — HTTP responses
- Signal handlers — no request context
- CLI commands — no Flask app
- Tests — no browser
If a service returned redirect(...), you couldn’t call it from a signal handler. By returning data, each caller decides how to present it.
Form helpers
For the common form POST pattern (validate → flash → redirect or re-render), routes use framework helpers:
# splent_feature_profile/routes.py
from splent_framework.utils.form_helpers import form_success, form_error
@profile_bp.route("/profile/edit", methods=["GET", "POST"])
@login_required
def edit_profile():
service = UserProfileService()
profile = service.get_by_user_id(current_user.id)
if not profile:
return redirect(url_for("public.index"))
form = UserProfileForm()
if request.method == "POST":
result, errors = service.update_profile(profile.id, form)
if result:
return form_success("profile.edit_profile", "Profile updated successfully")
return form_error("profile/edit.html", form, errors)
return render_template("profile/edit.html", form=form, profile=profile)
| Helper | What it does |
|---|---|
form_success(endpoint, message, **kwargs) |
Flash success message + redirect |
form_error(template, form, errors, **context) |
Flash field errors + re-render template |
Two lines instead of seven. Every form handler in the framework follows this pattern.
Service locator
Features register services during init_feature(app) so other features can discover and override them:
# splent_feature_notes/__init__.py
from splent_framework.services.service_locator import register_service
def init_feature(app):
register_service(app, "NotesService", NotesService)
Routes consume them via service_proxy:
from splent_framework.services.service_locator import service_proxy
notes_service = service_proxy("NotesService")
A refinement feature can override a service by registering the same name with a different class. See Extensibility.
See also
- Repositories — the layer services delegate to
- Blueprints — the layer that calls services
- Extensibility — service overrides via the service locator