- type
- concept
- created
- Tue Apr 07 2026 02:00:00 GMT+0200 (Central European Summer Time)
- updated
- Tue Apr 07 2026 02:00:00 GMT+0200 (Central European Summer Time)
- sources
- raw/notes/systemPatterns, raw/notes/techContext, raw/notes/codeGuidelines
- tags
- service-layer architecture pure-functions testing django design-pattern
Service Layer Pattern
abstract
Business logic lives in `apps/Overview
The Service Layer Pattern is the primary code organization pattern for business logic in the wiki/entities/b2bpaper backend. It was first introduced in the containers app and is intended to be replicated across all apps that contain non-trivial business logic.
The Pattern
Core Principles
- Business logic in
services.py, not in views or serializers - Services are pure functions -- stateless, testable without HTTP context
- Views call services, never the reverse
- Services return plain data (dicts, lists, model instances) -- not HTTP responses
- Each app gets its own
services.pywhen it has business logic
Code Organization
apps/<app>/
models.py # Data definitions
serializers.py # API serialization (explicit fields, never __all__)
views.py # HTTP layer, calls services
services.py # Business logic (pure functions)
tests/
test_services/ # Service unit tests
test_api/ # API integration tests
Why Pure Functions?
Pure functions (stateless, no side effects beyond their explicit purpose) provide several advantages:
- Testable without HTTP -- no need to construct request objects or mock middleware
- Composable -- services can call other services
- Readable -- business logic is not tangled with serialization or permission checks
- Portable -- can be called from management commands, Celery tasks, or views
Reference Implementation: Containers App
The containers app (apps/containers/services.py) was the first to implement this pattern and serves as the reference for all other apps:
calculate_container_gap()
- Pure math function, no database access
- Calculates remaining capacity in a container
find_fill_suggestions()
- Queries database for candidate surplus items
- Scores candidates on multiple dimensions
- Returns list of dicts (not model instances, not serialized responses)
calculate_freight_comparison()
- Database lookup for freight rates
- Fallback to hardcoded rates from PRD Section 8.6 when no DB rate exists
- Returns comparison data
build_fill_proposal()
- Orchestrator function: calls the three above functions
- Creates model instances (ContainerProposal, etc.)
- This is the only function that creates database records
Scoring System
The container fill scoring rates candidates 0-100:
- Paper type match: +20 points
- GSM closeness: +15 points
- Price advantage: +15 points
- Product compatibility checks: paper group, grade proximity (<=1), food contact
Pattern Replication
The pattern should be replicated for:
- Matching algorithm --
apps/matching/services.py(spec comparison, scoring, threshold filtering) - Ingestion parsing --
apps/ingestion/services.py(Excel parsing, field mapping, validation) - Newsletter generation --
apps/newsletters/services.py(template selection, content assembly) - Exclusivity management --
apps/exclusivity/services.py(window creation, expiry handling)
Testing Strategy
The pattern enables a clean two-tier testing approach:
tests/test_services/-- unit tests for pure business logic, fast, no HTTP overheadtests/test_api/-- integration tests for API endpoints, including auth, permissions, serialization
Sources
- raw/notes/systemPatterns -- pattern definition and containers reference implementation
- raw/notes/techContext -- container fill optimization as first implementation
- raw/notes/codeGuidelines -- testing and code organization standards
Related
- wiki/concepts/four-layer-architecture -- services implement the Action Layer
- wiki/concepts/eight-core-actions -- the actions that need service implementations
- wiki/concepts/django-app-layout -- the app structure where services.py lives
- wiki/summaries/codeguidelines-summary -- coding standards for service code
- wiki/summaries/systempatterns-summary -- broader architectural context