The one rule
Request flow
The four layers
1. Route — api/routes/<name>_route.py
Routes are Flask Blueprints. They receive HTTP requests, call the service, and return a JSON response. Nothing else.
- No business logic
- No database queries
- No imports from repositories or models
- Only import from the service layer
2. Service — api/services/<name>_service.py
Services contain all business logic: validation, calculations, transformations, and orchestration. They call repositories to read and write data.
- All business logic lives here
- No direct database access (no SQLAlchemy queries)
- Call repository methods only
- Raise
APIExceptionfor expected errors
3. Repository — api/repositories/<name>_repository.py
Repositories own all database interaction. They extend the Repository base class and use DBSession for queries. They return model instances.
- All SQLAlchemy queries live here
- No business logic, no validation
- Return model instances
- Use
self.add(),self.delete(),self.add_all()from the base class
4. Model — api/models/<Name>_model.py
Models are SQLAlchemy dataclasses that define the database table schema. They inherit from Base and contain only column definitions.
- No methods (except SQLAlchemy defaults)
- No business logic
- Column definitions only
File naming by resource
For a resource namedusers:
| Layer | File |
|---|---|
| Route | api/routes/users_route.py |
| Service | api/services/users_service.py |
| Repository | api/repositories/users_repository.py |
| Model | api/models/Users_model.py (capitalized) |
| Identifier | Value |
|---|---|
| Blueprint | users_bp |
| URL prefix | /users |
| Service class | UsersService |
| Service singleton | myUsersService |
| Repository class | UsersRepository |
| Repository singleton | myUsersRepository |
| Model class | Users |
| Table name | users |
Dependency direction
Where does this code go?
| Pattern | Layer |
|---|---|
if not data.get("email") | Service |
SELECT * FROM users | Repository |
Column(String, unique=True) | Model |
return jsonify(response) | Route |
@token_required decorator | Route |
raise APIException(400, "...") | Service |
self.session.commit() | DBSession (never call directly) |
logging.info(...) | Service or Repository via myLogger |