177 lines
4.5 KiB
Markdown
177 lines
4.5 KiB
Markdown
# Dependency Injection System
|
|
|
|
This directory contains the new dependency injection system for the GridPilot website, built with InversifyJS.
|
|
|
|
## Overview
|
|
|
|
The DI system provides:
|
|
- **Centralized dependency management** - All services are registered in modules
|
|
- **Type-safe injection** - Compile-time validation of dependencies
|
|
- **Easy testing** - Simple mocking via container overrides
|
|
- **React integration** - Hooks for component-level injection
|
|
- **NestJS-like patterns** - Familiar structure for API developers
|
|
|
|
## Architecture
|
|
|
|
```
|
|
lib/di/
|
|
├── index.ts # Main exports
|
|
├── container.ts # Container factory & lifecycle
|
|
├── tokens.ts # Symbol-based tokens
|
|
├── providers/ # React Context integration
|
|
│ └── ContainerProvider.tsx
|
|
├── hooks/ # Injection hooks
|
|
│ └── useInject.ts
|
|
└── modules/ # Domain modules
|
|
├── core.module.ts # Logger, error reporter, config
|
|
├── api.module.ts # API clients
|
|
├── league.module.ts # League services
|
|
├── driver.module.ts # Driver services
|
|
└── team.module.ts # Team services
|
|
```
|
|
|
|
## Usage
|
|
|
|
### 1. Setup Root Provider
|
|
|
|
```tsx
|
|
// app/layout.tsx
|
|
import { ContainerProvider } from '@/lib/di/providers/ContainerProvider';
|
|
|
|
export default function RootLayout({ children }) {
|
|
return (
|
|
<html>
|
|
<body>
|
|
<ContainerProvider>
|
|
{children}
|
|
</ContainerProvider>
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|
|
```
|
|
|
|
### 2. Inject Services in Hooks
|
|
|
|
```typescript
|
|
// hooks/useLeagueService.ts
|
|
import { useInject } from '@/lib/di/hooks/useInject';
|
|
import { LEAGUE_SERVICE_TOKEN } from '@/lib/di/tokens';
|
|
import { useQuery } from '@tanstack/react-query';
|
|
|
|
export function useAllLeagues() {
|
|
const leagueService = useInject(LEAGUE_SERVICE_TOKEN);
|
|
|
|
return useQuery({
|
|
queryKey: ['allLeagues'],
|
|
queryFn: () => leagueService.getAllLeagues(),
|
|
});
|
|
}
|
|
```
|
|
|
|
### 3. Inject in Components
|
|
|
|
```typescript
|
|
// components/MyComponent.tsx
|
|
'use client';
|
|
|
|
import { useInject } from '@/lib/di/hooks/useInject';
|
|
import { LEAGUE_SERVICE_TOKEN } from '@/lib/di/tokens';
|
|
|
|
export function MyComponent() {
|
|
const leagueService = useInject(LEAGUE_SERVICE_TOKEN);
|
|
|
|
// Use leagueService...
|
|
}
|
|
```
|
|
|
|
### 4. Server Components
|
|
|
|
```typescript
|
|
// app/leagues/[id]/page.tsx
|
|
import { createContainer } from '@/lib/di/container';
|
|
import { LEAGUE_SERVICE_TOKEN } from '@/lib/di/tokens';
|
|
|
|
export default async function LeaguePage({ params }) {
|
|
const container = createContainer();
|
|
const leagueService = container.get(LEAGUE_SERVICE_TOKEN);
|
|
|
|
const league = await leagueService.getLeague(params.id);
|
|
return <ClientComponent league={league} />;
|
|
}
|
|
```
|
|
|
|
### 5. Testing
|
|
|
|
```typescript
|
|
import { createTestContainer } from '@/lib/di/container';
|
|
import { ContainerProvider } from '@/lib/di/providers/ContainerProvider';
|
|
import { LEAGUE_SERVICE_TOKEN } from '@/lib/di/tokens';
|
|
|
|
test('component works', () => {
|
|
const mockService = { getData: jest.fn() };
|
|
const overrides = new Map([
|
|
[LEAGUE_SERVICE_TOKEN, mockService]
|
|
]);
|
|
|
|
const container = createTestContainer(overrides);
|
|
|
|
render(
|
|
<ContainerProvider container={container}>
|
|
<MyComponent />
|
|
</ContainerProvider>
|
|
);
|
|
});
|
|
```
|
|
|
|
## Token Naming Convention
|
|
|
|
```typescript
|
|
// Format: DOMAIN_SERVICE_TYPE_TOKEN
|
|
export const LEAGUE_SERVICE_TOKEN = Symbol.for('Service.League');
|
|
export const LEAGUE_API_CLIENT_TOKEN = Symbol.for('Api.LeagueClient');
|
|
export const LOGGER_TOKEN = Symbol.for('Core.Logger');
|
|
```
|
|
|
|
## Module Pattern
|
|
|
|
```typescript
|
|
import { ContainerModule } from 'inversify';
|
|
import { Service } from './Service';
|
|
import { SERVICE_TOKEN } from '../tokens';
|
|
|
|
export const DomainModule = new ContainerModule((options) => {
|
|
const bind = options.bind;
|
|
|
|
bind<SERVICE_TOKEN>(SERVICE_TOKEN)
|
|
.to(Service)
|
|
.inSingletonScope();
|
|
});
|
|
```
|
|
|
|
## Migration from Old System
|
|
|
|
### Before
|
|
```typescript
|
|
const { leagueService } = useServices();
|
|
```
|
|
|
|
### After
|
|
```typescript
|
|
const leagueService = useInject(LEAGUE_SERVICE_TOKEN);
|
|
```
|
|
|
|
## Benefits
|
|
|
|
✅ **Testability** - Easy mocking via container overrides
|
|
✅ **Maintainability** - Clear dependency graphs
|
|
✅ **Type Safety** - Compile-time validation
|
|
✅ **Consistency** - Same patterns as NestJS API
|
|
✅ **Performance** - Singleton scope by default
|
|
|
|
## Next Steps
|
|
|
|
1. Complete module implementations for all domains
|
|
2. Migrate all React-Query hooks to use `useInject()`
|
|
3. Update tests to use test containers
|
|
4. Remove old `ServiceProvider` and `ServiceFactory` |