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


Back to top

splent. Distributed by an LGPL license v3. Contact us: drorganvidez@us.es