Skip to content

Service Layer Contract

Responsibilities

Service layer owns: - business rules - orchestration across repo operations - domain error generation

Example

def get_user(self, user_id: int):
    user = self.repo.get_by_id(user_id)
    if user is None:
        raise NotFoundError(f"User {user_id} was not found")
    return user

DO

  • Keep service methods named by business intent (create_user, deactivate_user).
  • Validate business invariants here.
  • Raise domain exceptions, not HTTP errors.

DON'T

  • Put HTTP request objects in service signatures.
  • Put SQL query composition in service.

Common Mistakes

  • Fake service layer that just forwards repo.method(...) with no rules.
  • Service committing raw transaction boundaries through direct DB session access.

When to Break the Rule

Read-only pass-through methods are acceptable if the business rule is truly empty and likely to stay empty. Document this in code review.