# Website Services Architecture This document defines the role and responsibilities of services in the website layer (`apps/website/lib/services/`). ## Overview Website services are **frontend orchestration services**. They bridge the gap between server-side composition (PageQueries, Server Actions) and API infrastructure. ## Purpose Website services answer: **"How does the website orchestrate API calls and handle infrastructure?"** ## Responsibilities ### ✅ Services MAY: - Call API clients - Orchestrate multiple API calls - Handle infrastructure concerns (logging, error reporting, retries) - Transform API DTOs to Page DTOs (if orchestration is needed) - Cache responses (in-memory, request-scoped) - Handle recoverable errors ### ❌ Services MUST NOT: - Contain business rules (that's for core use cases) - Create ViewModels (ViewModels are client-only) - Import from `lib/view-models/` or `templates/` - Perform UI rendering logic - Store state across requests ## Placement ``` apps/website/lib/services/ ``` ## Pattern ### Service Definition ```typescript import { AdminApiClient } from '@/lib/api/admin/AdminApiClient'; import type { UserDto } from '@/lib/api/admin/AdminApiClient'; export class AdminService { constructor(private readonly apiClient: AdminApiClient) {} async updateUserStatus(userId: string, status: string): Promise { return this.apiClient.updateUserStatus(userId, status); } } ``` ### Usage in PageQueries (Reads) ```typescript // apps/website/lib/page-queries/AdminDashboardPageQuery.ts import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger'; import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter'; import { AdminApiClient } from '@/lib/api/admin/AdminApiClient'; import { AdminService } from '@/lib/services/admin/AdminService'; export class AdminDashboardPageQuery { async execute(): Promise> { // Create infrastructure const logger = new ConsoleLogger(); const errorReporter = new EnhancedErrorReporter(logger, {...}); const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001'; const apiClient = new AdminApiClient(baseUrl, errorReporter, logger); const service = new AdminService(apiClient); // Use service const stats = await service.getDashboardStats(); // Transform to Page DTO return { status: 'ok', dto: transformToPageDto(stats) }; } } ``` ### Usage in Server Actions (Writes) ```typescript // apps/website/app/admin/actions.ts 'use server'; import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger'; import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter'; import { AdminApiClient } from '@/lib/api/admin/AdminApiClient'; import { AdminService } from '@/lib/services/admin/AdminService'; import { revalidatePath } from 'next/cache'; export async function updateUserStatus(userId: string, status: string): Promise { try { // Create infrastructure const logger = new ConsoleLogger(); const errorReporter = new EnhancedErrorReporter(logger, { showUserNotifications: true, logToConsole: true, reportToExternal: process.env.NODE_ENV === 'production', }); const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001'; const apiClient = new AdminApiClient(baseUrl, errorReporter, logger); const service = new AdminService(apiClient); // Use service (NOT API client directly) await service.updateUserStatus(userId, status); // Revalidate revalidatePath('/admin/users'); } catch (error) { console.error('updateUserStatus failed:', error); throw new Error('Failed to update user status'); } } ``` ## Infrastructure Concerns **Where should logging/error reporting live?** In the current architecture, **server actions and PageQueries create infrastructure**. This is acceptable because: 1. Next.js serverless functions are stateless 2. Each request needs fresh infrastructure 3. Manual DI is clearer than magic containers **Key principle**: Services orchestrate, they don't create infrastructure. ## Dependency Chain ``` Server Action / PageQuery ↓ (creates infrastructure) Service ↓ (orchestrates) API Client ↓ (makes HTTP calls) API ``` ## Naming - Service classes: `*Service` - Service methods: Return DTOs (not ViewModels) - Variable names: `apiDto`, `pageDto` (never just `dto`) ## Comparison with Other Layers | Layer | Purpose | Example | |-------|---------|---------| | **Website Service** | Orchestrate API calls | `AdminService` | | **API Client** | HTTP infrastructure | `AdminApiClient` | | **Core Use Case** | Business rules | `CreateLeagueUseCase` | | **Domain Service** | Cross-entity logic | `StrengthOfFieldCalculator` | ## Anti-Patterns ❌ **Wrong**: Service creates ViewModels ```typescript // WRONG class AdminService { async getUser(userId: string): Promise { const dto = await this.apiClient.getUser(userId); return new UserViewModel(dto); // ❌ ViewModels are client-only } } ``` ✅ **Correct**: Service returns DTOs ```typescript // CORRECT class AdminService { async getUser(userId: string): Promise { return this.apiClient.getUser(userId); // ✅ DTOs are fine } } ``` ❌ **Wrong**: Service contains business logic ```typescript // WRONG class AdminService { async canDeleteUser(userId: string): Promise { const user = await this.apiClient.getUser(userId); return user.role !== 'admin'; // ❌ Business rule belongs in core } } ``` ✅ **Correct**: Service orchestrates ```typescript // CORRECT class AdminService { async getUser(userId: string): Promise { return this.apiClient.getUser(userId); } } // Business logic in core use case or page query ``` ❌ **Wrong**: Server action calls API client directly ```typescript // WRONG 'use server'; export async function updateUserStatus(userId: string, status: string) { const apiClient = new AdminApiClient(...); await apiClient.updateUserStatus(userId, status); // ❌ Should use service } ``` ✅ **Correct**: Server action uses service ```typescript // CORRECT 'use server'; export async function updateUserStatus(userId: string, status: string) { const apiClient = new AdminApiClient(...); const service = new AdminService(apiClient); await service.updateUserStatus(userId, status); // ✅ Uses service } ``` ## Summary Website services are **thin orchestration wrappers** around API clients. They handle infrastructure concerns so that PageQueries and Server Actions can focus on composition and validation. **Key principles**: 1. Services orchestrate API calls 2. Server actions/PageQueries create infrastructure 3. Services don't create ViewModels 4. Services don't contain business rules 5. **Server actions MUST use services, not API clients directly**