website refactor

This commit is contained in:
2026-01-13 02:42:58 +01:00
parent 38b25bafe1
commit b82736b24b
6 changed files with 661 additions and 251 deletions

View File

@@ -62,11 +62,15 @@ class HttpServerError extends HttpError {}
### Layer 2: Service (Technical → Domain Errors)
The Service catches HTTP errors and converts them to domain errors:
The Service creates its own dependencies and converts HTTP errors to domain errors.
**See**: [DEPENDENCY_CONSTRUCTION.md](./DEPENDENCY_CONSTRUCTION.md) for why Services create their own dependencies.
```typescript
// apps/website/lib/services/dashboard/DashboardService.ts
import { DashboardApiClient } from '@/lib/api/dashboard/DashboardApiClient';
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
export type DashboardServiceError =
| { type: 'notFound'; message: string }
@@ -76,7 +80,17 @@ export type DashboardServiceError =
| { type: 'unknown'; message: string };
export class DashboardService {
constructor(private apiClient: DashboardApiClient) {}
private apiClient: DashboardApiClient;
constructor() {
// Service creates its own dependencies
const baseUrl = process.env.NEXT_PUBLIC_API_URL || '';
this.apiClient = new DashboardApiClient(
baseUrl,
new ConsoleErrorReporter(),
new ConsoleLogger()
);
}
async getDashboardOverview(): Promise<Result<DashboardStats, DashboardServiceError>> {
try {
@@ -105,9 +119,17 @@ export class DashboardService {
}
```
**Key Points:**
- ✅ Service creates its own API Client
- ✅ Service creates its own Logger and ErrorReporter
- ✅ Catches HTTP errors and converts to domain errors
- ✅ Returns Result type
### Layer 3: PageQuery (Domain → Presentation Errors)
PageQueries use Services and map domain errors to presentation errors:
PageQueries use Services and map domain errors to presentation errors.
**See**: [DEPENDENCY_CONSTRUCTION.md](./DEPENDENCY_CONSTRUCTION.md) for why we use manual construction.
```typescript
// apps/website/lib/page-queries/page-queries/DashboardPageQuery.ts
@@ -118,12 +140,8 @@ type DashboardPageError = 'notFound' | 'redirect' | 'DASHBOARD_FETCH_FAILED' | '
export class DashboardPageQuery implements PageQuery<DashboardViewData, void> {
async execute(): Promise<Result<DashboardViewData, DashboardPageError>> {
// Manual wiring
const errorReporter = new ConsoleErrorReporter();
const logger = new ConsoleLogger();
const baseUrl = process.env.NEXT_PUBLIC_API_URL || '';
const apiClient = new DashboardApiClient(baseUrl, errorReporter, logger);
const dashboardService = new DashboardService(apiClient);
// Manual construction: Service creates its own dependencies
const dashboardService = new DashboardService();
// Call service
const serviceResult = await dashboardService.getDashboardOverview();
@@ -154,6 +172,13 @@ export class DashboardPageQuery implements PageQuery<DashboardViewData, void> {
}
```
**Key Points:**
- ✅ PageQuery constructs only the Service
- ✅ Service handles its own dependencies (API Client, Logger, etc.)
- ❌ No API Client instantiation in PageQuery
- ✅ Map domain errors to presentation errors
- ✅ Transform API DTO to ViewData using Builder
### Layer 4: RSC Page (Presentation → User)
The RSC page handles presentation errors:
@@ -282,6 +307,10 @@ export class UserApiClient {
```typescript
// apps/website/lib/services/user/UserService.ts
import { UserApiClient } from '@/lib/api/user/UserApiClient';
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
export type UserServiceError =
| { type: 'notFound'; message: string }
| { type: 'forbidden'; message: string }
@@ -289,7 +318,17 @@ export type UserServiceError =
| { type: 'serverError'; message: string };
export class UserService {
constructor(private apiClient: UserApiClient) {}
private apiClient: UserApiClient;
constructor() {
// Service creates its own dependencies
const baseUrl = process.env.NEXT_PUBLIC_API_URL || '';
this.apiClient = new UserApiClient(
baseUrl,
new ConsoleErrorReporter(),
new ConsoleLogger()
);
}
async updateUserStatus(userId: string, status: string): Promise<Result<void, UserServiceError>> {
try {
@@ -314,6 +353,12 @@ export class UserService {
}
```
**Key Points:**
- ✅ Service creates its own API Client
- ✅ Service creates its own Logger and ErrorReporter
- ✅ Catches HTTP errors and converts to domain errors
- ✅ Returns Result type
### Layer 3: Mutation (Domain → Presentation Errors)
```typescript
@@ -358,18 +403,11 @@ export class UpdateUserStatusMutation implements Mutation<UpdateUserStatusInput,
'use server';
import { UpdateUserStatusMutation } from '@/lib/mutations/UpdateUserStatusMutation';
import { UserService } from '@/lib/services/user/UserService';
import { UserApiClient } from '@/lib/api/user/UserApiClient';
import { revalidatePath } from 'next/cache';
export async function updateUserStatus(input: UpdateUserStatusInput) {
// Manual wiring
const errorReporter = new ConsoleErrorReporter();
const logger = new ConsoleLogger();
const baseUrl = process.env.NEXT_PUBLIC_API_URL || '';
const apiClient = new UserApiClient(baseUrl, errorReporter, logger);
const userService = new UserService(apiClient);
const mutation = new UpdateUserStatusMutation(userService);
// Manual construction: Mutation creates Service, Service creates dependencies
const mutation = new UpdateUserStatusMutation();
const result = await mutation.execute(input);
@@ -384,6 +422,12 @@ export async function updateUserStatus(input: UpdateUserStatusInput) {
}
```
**Key Points:**
- ✅ Server Action constructs only the Mutation
- ✅ Mutation constructs the Service
- ✅ Service constructs its own dependencies
- ✅ No manual wiring needed
### Layer 5: Client Component (Handles Result)
```typescript