Skip to main content
FootyCollect uses a Service Layer pattern to encapsulate business logic and keep views focused on HTTP concerns. This architectural pattern separates business rules from presentation logic, making the codebase more maintainable and testable.

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
This makes views hard to test and maintain.

The Solution

The service layer pattern addresses these issues by:
  1. Separation of Concerns: Views handle HTTP, services handle business logic
  2. Reusability: Services can be used from views, management commands, API endpoints, and Celery tasks
  3. Testability: Business logic can be tested without the HTTP layer
  4. Maintainability: Changes to business logic are localized in services
  5. 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 Registry

Services are accessed through a centralized registry that enables dependency injection and testability.

Registry Pattern

Source: 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:
This ensures no state is shared between requests.

Transaction Management

Services handle database transactions internally using Django’s transaction.atomic():
Source: footycollect/collection/services/item_service.py:33-61

Error Handling

Services raise domain-specific exceptions that views can catch:
Source: footycollect/collection/services/photo_service.py:224-257

Service Implementations

ItemService

Handles item creation, updates, deletion, and queries:
Source: footycollect/collection/services/item_service.py:20-176

PhotoService

Manages photo uploads and processing:
Source: 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:
  1. Keep Views Thin: Views should only handle HTTP concerns (request/response)
  2. Use Services for Complex Logic: Any operation involving multiple models or business rules
  3. Handle Transactions in Services: Services define transaction boundaries with transaction.atomic()
  4. Raise Domain Exceptions: Use descriptive exceptions (ValueError, etc.) for business rule violations
  5. Test Services Independently: Write unit tests for services without involving views
  6. Avoid Service-to-Service Calls: Services should coordinate repositories, not other services
  7. 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)
Views are sufficient when:
  • 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:
This layering ensures:
  • Services focus on business logic
  • Repositories handle data access
  • Clear separation of concerns
  • Easier to test and maintain