website refactor
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user