di usage in website
This commit is contained in:
177
apps/website/lib/di/README.md
Normal file
177
apps/website/lib/di/README.md
Normal file
@@ -0,0 +1,177 @@
|
||||
# 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`
|
||||
Reference in New Issue
Block a user