Overview
The service layer sits between views and models, orchestrating complex operations that involve multiple models, external APIs, or background tasks.Why Service Layer?
The Problem
Without a service layer, views often become bloated with:- Multiple model interactions
- Complex transaction management
- External API integration logic
- Image processing workflows
- Business rule validation
The Solution
The service layer pattern addresses these issues by:- Separation of Concerns: Views handle HTTP, services handle business logic
- Reusability: Services can be used from views, management commands, API endpoints, and Celery tasks
- Testability: Business logic can be tested without the HTTP layer
- Maintainability: Changes to business logic are localized in services
- Transaction Management: Services define clear transaction boundaries
This architectural decision is documented in ADR 0002: Service Layer Pattern.
Available Services
FootyCollect provides several service classes:| Service | Purpose |
|---|---|
ItemService | Item CRUD operations and collection management |
PhotoService | Photo upload, processing, and management |
CollectionService | Collection-level operations and analytics |
ItemFKAPIService | External Football Kit Archive API integration |
ColorService | Color management |
SizeService | Size management |
Service Registry
Services are accessed through a centralized registry that enables dependency injection and testability.Registry Pattern
footycollect/collection/services/service_registry.py:17-92
Accessing Services
Service Interface
All services follow a consistent pattern:Stateless Design
Services are stateless classes, instantiated per operation:Transaction Management
Services handle database transactions internally using Django’stransaction.atomic():
footycollect/collection/services/item_service.py:33-61
Error Handling
Services raise domain-specific exceptions that views can catch:footycollect/collection/services/photo_service.py:224-257
Service Implementations
ItemService
Handles item creation, updates, deletion, and queries:footycollect/collection/services/item_service.py:20-176
PhotoService
Manages photo uploads and processing:footycollect/collection/services/photo_service.py:30-69
ItemFKAPIService
Integrates with external Football Kit Archive API:Using Services in Views
Views should delegate business logic to services and focus on HTTP concerns:Class-Based View Example
API View Example
Testing Services
Services are easily testable in isolation without HTTP layer:Best Practices
Guidelines for using the service layer:
- Keep Views Thin: Views should only handle HTTP concerns (request/response)
- Use Services for Complex Logic: Any operation involving multiple models or business rules
- Handle Transactions in Services: Services define transaction boundaries with
transaction.atomic() - Raise Domain Exceptions: Use descriptive exceptions (ValueError, etc.) for business rule violations
- Test Services Independently: Write unit tests for services without involving views
- Avoid Service-to-Service Calls: Services should coordinate repositories, not other services
- Simple CRUD Can Stay in Views: Not everything needs a service; use judgment
When to Use Services
Use a service when:- Operation involves multiple models
- Complex business logic or validation
- External API integration
- Background task coordination
- Transaction spans multiple operations
- Logic needs to be reused (views, APIs, tasks)
- Simple CRUD operations on a single model
- No complex business rules
- Standard Django form processing
- Direct ORM queries suffice
Service Dependencies
Services use repositories for data access:- Services focus on business logic
- Repositories handle data access
- Clear separation of concerns
- Easier to test and maintain