# Dependency Construction Architecture

## The Decision: Manual Construction (Not DI Container)

### Why Not Dependency Injection in RSC?

**Problem**: Next.js RSC pages run on every request, but DI containers are singletons.

**Risks with DI:**
1. **Data Leakage**: Singleton container could share auth tokens between users
2. **Request Context**: Can't inject request-specific data (user ID, auth token)
3. **Scoping Complexity**: Would need request-scoped containers
4. **Overhead**: DI adds complexity without benefit for RSC

**Example of the Problem:**
```typescript
// ❌ DON'T DO THIS - DI in RSC
export default async function DashboardPage() {
  const container = ContainerManager.getInstance().getContainer();
  const service = container.get<DashboardService>(DASHBOARD_SERVICE_TOKEN);
  
  // Problem: What if DashboardService needs the user's auth token?
  // The singleton container doesn't know which user this request is for!
}
```

### The Solution: Manual Construction

**Pattern:**
```
RSC Page
  ↓
PageQuery (constructs Service)
  ↓
Service (constructs API Client, Logger, ErrorReporter)
  ↓
API Client (makes HTTP calls)
```

**Example:**
```typescript
// ✅ CORRECT: Manual construction in RSC
export default async function DashboardPage() {
  const query = new DashboardPageQuery();
  const result = await query.execute();
  // ...
}

// In DashboardPageQuery
export class DashboardPageQuery {
  async execute() {
    const service = new DashboardService(); // Manual construction
    return await service.getDashboardOverview();
  }
}

// In DashboardService
export class DashboardService {
  private apiClient: DashboardApiClient;
  
  constructor() {
    // Service creates its own dependencies
    this.apiClient = new DashboardApiClient(
      process.env.NEXT_PUBLIC_API_URL || '',
      new ConsoleErrorReporter(),
      new ConsoleLogger()
    );
  }
  
  async getDashboardOverview() {
    return await this.apiClient.getOverview();
  }
}
```

### Benefits of Manual Construction

1. **Explicit**: Dependencies are clear and visible
2. **Simple**: No magic, no container, no configuration
3. **Safe**: No singleton issues, no data leakage
4. **Testable**: Easy to pass mocks in constructor
5. **Flexible**: Can change dependencies without affecting callers

### What About the Existing `lib/di/`?

The project has an Inversify DI system, but it's designed for:

**Client Components:**
```typescript
// ✅ OK in client components
export function UserDashboard() {
  const service = useInject(DASHBOARD_SERVICE_TOKEN);
  // ...
}
```

**Testing:**
```typescript
// ✅ OK in tests
const mockApiClient = new MockDashboardApiClient();
const service = new DashboardService(mockApiClient);
```

**RSC (PageQueries/Mutations):**
```typescript
// ❌ DON'T use DI container in RSC
// ✅ DO use manual construction
```

### ESLint Enforcement

The `services-no-instantiation` rule is **removed** because it was wrong.

**Correct Rules:**
- ✅ `clean-error-handling`: PageQueries must use Services, not API Clients
- ✅ `services-implement-contract`: Services must return Result types
- ✅ `lib-no-next-imports`: No Next.js imports in lib/ directory

**What the Rules Allow:**
```typescript
// In PageQuery - ALLOWED
const service = new DashboardService();

// In Service - ALLOWED
this.apiClient = new DashboardApiClient(...);
this.logger = new ConsoleLogger();

// In PageQuery - FORBIDDEN
const apiClient = new DashboardApiClient(...); // Use Service instead!
```

### Complete Example: Read Flow

```typescript
// apps/website/app/dashboard/page.tsx
export default async function DashboardPage() {
  const query = new DashboardPageQuery();
  const result = await query.execute();
  
  if (result.isErr()) {
    // Handle presentation errors
    return <ErrorDashboard error={result.error} />;
  }
  
  return <DashboardTemplate viewData={result.value} />;
}

// apps/website/lib/page-queries/page-queries/DashboardPageQuery.ts
export class DashboardPageQuery implements PageQuery<DashboardViewData, void> {
  async execute(): Promise<Result<DashboardViewData, DashboardPageError>> {
    const service = new DashboardService();
    const result = await service.getDashboardOverview();
    
    if (result.isErr()) {
      // Map domain error to presentation error
      return Result.err(mapToPresentationError(result.error));
    }
    
    const viewData = DashboardViewDataBuilder.build(result.value);
    return Result.ok(viewData);
  }
}

// apps/website/lib/services/analytics/DashboardService.ts
export class DashboardService {
  private apiClient: DashboardApiClient;

  constructor() {
    this.apiClient = new DashboardApiClient(
      process.env.NEXT_PUBLIC_API_URL || '',
      new ConsoleErrorReporter(),
      new ConsoleLogger()
    );
  }

  async getDashboardOverview(): Promise<Result<DashboardOverviewDTO, DomainError>> {
    try {
      const data = await this.apiClient.getOverview();
      return Result.ok(data);
    } catch (error) {
      // Convert HTTP errors to domain errors
      if (error instanceof HttpNotFoundError) {
        return Result.err(new NotFoundError('Dashboard not found'));
      }
      return Result.err(new UnknownError('Failed to fetch dashboard'));
    }
  }
}

// apps/website/lib/api/dashboard/DashboardApiClient.ts
export class DashboardApiClient {
  constructor(
    private baseUrl: string,
    private errorReporter: ErrorReporter,
    private logger: Logger
  ) {}

  async getOverview(): Promise<DashboardOverviewDTO> {
    const response = await fetch(`${this.baseUrl}/dashboard/overview`);
    
    if (!response.ok) {
      if (response.status === 404) {
        throw new HttpNotFoundError('Dashboard not found');
      }
      throw new HttpError(`HTTP ${response.status}`);
    }
    
    return response.json();
  }
}
```

### Complete Example: Write Flow

```typescript
// apps/website/app/actions/userActions.ts
'use server';

export async function updateUserStatus(input: UpdateUserStatusInput) {
  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 };
}

// apps/website/lib/mutations/UpdateUserStatusMutation.ts
export class UpdateUserStatusMutation implements Mutation<UpdateUserStatusInput, void> {
  async execute(input: UpdateUserStatusInput): Promise<Result<void, MutationError>> {
    const service = new UserService();
    const result = await service.updateUserStatus(input.userId, input.status);
    
    if (result.isErr()) {
      return Result.err(mapToMutationError(result.error));
    }
    
    return Result.ok(undefined);
  }
}

// apps/website/lib/services/user/UserService.ts
export class UserService {
  private apiClient: UserApiClient;

  constructor() {
    this.apiClient = new UserApiClient(
      process.env.NEXT_PUBLIC_API_URL || '',
      new ConsoleErrorReporter(),
      new ConsoleLogger()
    );
  }

  async updateUserStatus(userId: string, status: string): Promise<Result<void, DomainError>> {
    try {
      await this.apiClient.updateUserStatus(userId, status);
      return Result.ok(undefined);
    } catch (error) {
      if (error instanceof HttpForbiddenError) {
        return Result.err(new ForbiddenError('Insufficient permissions'));
      }
      return Result.err(new UnknownError('Failed to update user'));
    }
  }
}
```

### Summary

| Aspect | RSC (PageQueries/Mutations) | Client Components |
|--------|----------------------------|-------------------|
| **Construction** | Manual (`new Service()`) | Manual or DI hooks |
| **DI Container** | ❌ Never use | ✅ Can use |
| **Dependencies** | Service creates its own | Injected or manual |
| **Testability** | Pass mocks to constructor | Use DI mocks |
| **Complexity** | Low | Medium |

**Golden Rule**: In RSC, always use manual construction. It's simpler, safer, and more explicit.
