4.3 KiB
Services Design Guide (Clean Architecture)
This document defines all service types used across the system and assigns clear, non-overlapping responsibilities.
It exists to remove ambiguity around the word “service”, which is heavily overloaded.
The rules below are strict.
⸻
Overview
The system contains four distinct service categories, each in a different layer: 1. Frontend Services 2. API Services 3. Core Application Services 4. Core Domain Services
They must never be mixed.
⸻
- Frontend Services
Purpose
Frontend services orchestrate UI-driven workflows.
They answer the question:
“How does the UI obtain and submit data?”
⸻
Responsibilities
Frontend services MAY: • call API clients • apply client-side guards (blockers, throttles) • create View Models • orchestrate multiple API calls • handle recoverable UI errors
Frontend services MUST NOT: • contain business rules • validate domain invariants • modify domain state • know about core domain objects
⸻
Placement
apps/website/lib/services/
⸻
Example • LeagueService • RaceService • AuthService
Each service is UI-facing, not business-facing.
⸻
- API Services (Application Services)
Purpose
API services adapt HTTP-level concerns to core use cases.
They answer the question:
“How does an external client interact with the core?”
⸻
Responsibilities
API services MAY: • orchestrate multiple use cases • perform authorization checks • map transport input to use-case input • coordinate transactions
API services MUST NOT: • contain domain logic • enforce business invariants • build domain entities • return domain objects
⸻
Placement
apps/api/**/ApplicationService.ts
⸻
Example • LeagueApplicationService • SeasonApplicationService
API services are delivery-layer coordinators.
⸻
- Core Application Services (Use Cases)
Purpose
Core application services implement business use cases.
They answer the question:
“What does the system do?”
⸻
Responsibilities
Use Cases MUST: • accept primitive input only • create Value Objects • create or modify Entities • enforce business rules • call repositories via ports • communicate results via output ports
Use Cases MUST NOT: • know about HTTP, UI, or frameworks • return DTOs • perform persistence directly
⸻
Placement
core//application/commands/ core//application/queries/
⸻
Example • CreateLeagueUseCase • ApplyPenaltyUseCase • GetLeagueStandingsQuery
Use Cases define system behavior.
⸻
- Core Domain Services
Purpose
Domain services encapsulate domain logic that does not belong to a single entity.
They answer the question:
“What rules exist that span multiple domain objects?”
⸻
Responsibilities
Domain services MAY: • coordinate multiple entities • compute derived domain values • enforce cross-aggregate rules
Domain services MUST: • use only domain concepts • return domain objects or primitives
Domain services MUST NOT: • access repositories • depend on application services • perform IO
⸻
Placement
core//domain/services/
⸻
Example • SeasonConfigurationFactory • ChampionshipAggregator • StrengthOfFieldCalculator
Domain services protect business integrity.
⸻
Dependency Rules (Non-Negotiable)
Frontend Service → API Client → API Service → Core Use Case → Domain Service / Entity
Reverse dependencies are forbidden.
⸻
Anti-Patterns (Forbidden)
❌ Frontend calling core directly ❌ API service constructing domain entities ❌ Use case returning DTOs ❌ Domain service accessing repositories ❌ Single class acting as multiple service types
⸻
Naming Conventions
Layer Naming Frontend *Service API *ApplicationService Core Application *UseCase, *Query Core Domain *Service, *Factory, *Calculator
⸻
Mental Model (Final)
Services coordinate. Use Cases decide. Domain enforces truth. Adapters translate.
If a class violates this mental model, it is in the wrong layer.
⸻
Final Summary • “Service” means different things in different layers • Mixing service types causes architectural decay • Clean Architecture remains simple when roles stay pure
This document defines the only allowed service roles in the system.