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
// 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
// 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
// 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
// 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
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
// 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
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
const { leagueService } = useServices();
After
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
- Complete module implementations for all domains
- Migrate all React-Query hooks to use
useInject() - Update tests to use test containers
- Remove old
ServiceProviderandServiceFactory