# 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

Services create their own dependencies:

```typescript
import { AdminApiClient } from '@/lib/api/admin/AdminApiClient';
import type { UserDto } from '@/lib/api/admin/AdminApiClient';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';

export class AdminService {
  private apiClient: AdminApiClient;

  constructor() {
    // Service creates its own dependencies
    const baseUrl = process.env.NEXT_PUBLIC_API_URL || '';
    this.apiClient = new AdminApiClient(
      baseUrl,
      new ConsoleErrorReporter(),
      new ConsoleLogger()
    );
  }

  async updateUserStatus(userId: string, status: string): Promise<Result<UserDto, DomainError>> {
    try {
      const result = await this.apiClient.updateUserStatus(userId, status);
      return Result.ok(result);
    } catch (error) {
      // Convert HTTP errors to domain errors
      if (error instanceof HttpForbiddenError) {
        return Result.err(new ForbiddenError('Insufficient permissions'));
      }
      return Result.err(new UnknownError('Failed to update user'));
    }
  }
}
```

### Usage in PageQueries (Reads)

```typescript
// apps/website/lib/page-queries/AdminDashboardPageQuery.ts
import { AdminService } from '@/lib/services/admin/AdminService';
import { AdminDashboardViewDataBuilder } from '@/lib/builders/view-data/AdminDashboardViewDataBuilder';

export class AdminDashboardPageQuery implements PageQuery<AdminDashboardViewData, void> {
  async execute(): Promise<Result<AdminDashboardViewData, DashboardPageError>> {
    // Manual construction: Service creates its own dependencies
    const service = new AdminService();
    
    // Use service
    const result = await service.getDashboardStats();
    
    if (result.isErr()) {
      return Result.err(mapToPresentationError(result.error));
    }
    
    // Transform to ViewData using Builder
    const viewData = AdminDashboardViewDataBuilder.build(result.value);
    return Result.ok(viewData);
  }
}
```

### Usage in Mutations (Writes)

```typescript
// apps/website/lib/mutations/UpdateUserStatusMutation.ts
import { AdminService } from '@/lib/services/admin/AdminService';

export class UpdateUserStatusMutation implements Mutation<UpdateUserStatusInput, void> {
  async execute(input: UpdateUserStatusInput): Promise<Result<void, MutationError>> {
    // Manual construction: Service creates its own dependencies
    const service = new AdminService();
    
    const result = await service.updateUserStatus(input.userId, input.status);
    
    if (result.isErr()) {
      return Result.err(mapToMutationError(result.error));
    }
    
    return Result.ok(undefined);
  }
}
```

### Usage in Server Actions

```typescript
// app/admin/actions.ts
'use server';

import { UpdateUserStatusMutation } from '@/lib/mutations/UpdateUserStatusMutation';
import { revalidatePath } from 'next/cache';

export async function updateUserStatus(input: UpdateUserStatusInput) {
  // Manual construction: Mutation creates Service
  const mutation = new UpdateUserStatusMutation();
  
  const result = await mutation.execute(input);
  
  if (result.isErr()) {
    return { success: false, error: result.error };
  }
  
  revalidatePath('/admin/users');
  return { success: true };
}
```

## Dependency Chain

```
RSC Page / Server Action
  ↓ (manual construction)
PageQuery / Mutation
  ↓ (manual construction)
Service
  ↓ (creates own dependencies)
API Client
  ↓ (makes HTTP calls)
API
```

**Key Principle**: Each layer manually constructs the next layer. Services create their own infrastructure (API Client, Logger, ErrorReporter).

## Why Manual Construction?

**See**: [DEPENDENCY_CONSTRUCTION.md](./DEPENDENCY_CONSTRUCTION.md)

**Summary**:
- ✅ Explicit and clear
- ✅ No singleton issues
- ✅ No request-scoping problems
- ✅ Easy to test (pass mocks to constructor)
- ✅ Works perfectly with Next.js RSC
- ❌ No DI container needed

## Naming

- Service classes: `*Service`
- Service methods: Return `Result<T, DomainError>`
- Variable names: `apiDto`, `viewData` (never just `dto`)

## Comparison with Other Layers

| Layer | Purpose | Example |
|-------|---------|---------|
| **Website Service** | Orchestrate API calls, handle errors | `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<UserViewModel> {
    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<Result<UserDto, DomainError>> {
    try {
      const dto = await this.apiClient.getUser(userId);
      return Result.ok(dto); // ✅ DTOs are fine
    } catch (error) {
      return Result.err(new NotFoundError('User not found'));
    }
  }
}
```

❌ **Wrong**: Service contains business logic
```typescript
// WRONG
class AdminService {
  async canDeleteUser(userId: string): Promise<boolean> {
    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<Result<UserDto, DomainError>> {
    try {
      const dto = await this.apiClient.getUser(userId);
      return Result.ok(dto);
    } catch (error) {
      return Result.err(new NotFoundError('User not found'));
    }
  }
}
// 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 Mutation
```typescript
// CORRECT
'use server';
export async function updateUserStatus(input: UpdateUserStatusInput) {
  const mutation = new UpdateUserStatusMutation();
  const result = await mutation.execute(input);
  // ...
}
```

❌ **Wrong**: PageQuery creates API Client
```typescript
// WRONG
export class DashboardPageQuery {
  async execute() {
    const apiClient = new DashboardApiClient(...); // ❌ Should use Service
    return await apiClient.getOverview();
  }
}
```

✅ **Correct**: PageQuery uses Service
```typescript
// CORRECT
export class DashboardPageQuery {
  async execute() {
    const service = new DashboardService(); // ✅ Service creates API Client
    return await service.getDashboardOverview();
  }
}
```

## Summary

Website services are **thin orchestration wrappers** that create their own dependencies and handle error conversion.

**Key principles**:
1. ✅ Services create their own dependencies (API Client, Logger, ErrorReporter)
2. ✅ Services return `Result<T, DomainError>`
3. ✅ Services convert HTTP errors to Domain errors
4. ❌ Services don't create ViewModels
5. ❌ Services don't contain business rules
6. ✅ PageQueries/Mutations use Services, not API Clients directly
7. ✅ Manual construction (no DI container in RSC)