Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Moduły i Dependency Injection - organizacja legionu

Salve ponownie, legioniście! Teraz poznamy tajniki organizacji legionu w naszym Imperium. Każdy doświadczony konsul wie, że dobra organizacja i jasny podział obowiązków to klucz do sukcesu każdej kampanii!

Moduły - struktury naszego Imperium

W NestJS moduły to jak różne struktury Imperium - każda ma swoje przeznaczenie, ale wszystkie współpracują ze sobą, tworząc sprawnie działającą machinerię imperialną!

Podstawowy moduł

1// legion.module.ts - struktura legionu
2import { Module } from '@nestjs/common';
3import { LegionController } from './legion.controller';
4import { LegionService } from './legion.service';
5import { LegionaryValidator } from './legionary.validator';
6
7@Module({
8 controllers: [LegionController], // Centurionowie struktur
9 providers: [LegionService, LegionaryValidator], // Specjaliści i narzędzia
10 exports: [LegionService], // Co udostępniamy innym strukturom
11})
12export class LegionModule {}

Importowanie między modułami

Czasem jedna struktura Imperium potrzebuje pomocy od innej:

1// tributes.module.ts - struktura trybutów
2@Module({
3 imports: [LegionModule], // Importujemy strukturę legionu
4 controllers: [TributesController],
5 providers: [
6 TributesService,
7 TributesMapper,
8 TributesGuard,
9 ],
10 exports: [TributesService],
11})
12export class TributesModule {}
13
14// W serwisie trybutów możemy używać serwisu legionu
15@Injectable()
16export class TributesService {
17 constructor(
18 private readonly legionService: LegionService, // Dostępne dzięki import + export
19 ) {}
20
21 async assignTributeCollection(provinceId: string) {
22 const availableLegionaries = await this.legionService.getAvailableLegionaries();
23 return this.legionService.assignMission(availableLegionaries[0], provinceId);
24 }
25}

Moduły globalne

Niektóre usługi potrzebne są w całym Imperium - jak system komunikacji:

1// communication.module.ts
2@Global() // Dostępne wszędzie bez importowania
3@Module({
4 providers: [
5 SignalService,
6 MessageService,
7 EmergencyService,
8 ],
9 exports: [SignalService, MessageService, EmergencyService],
10})
11export class CommunicationModule {}

Dependency Injection - łańcuch dowodzenia

DI w NestJS to jak hierarchia i podział obowiązków w legionie - każdy wie, od kogo brać rozkazy i komu przekazywać informacje.

Podstawowe wstrzykiwanie zależności

1// Serwis inżynieryjny - specjalista od tras
2@Injectable()
3export class EngineeringService {
4 private currentRoute: Route;
5
6 calculateRoute(from: Coordinates, to: Coordinates): Route {
7 // Skomplikowane obliczenia trasy (Via Appia)
8 return {
9 distance: this.calculateDistance(from, to),
10 estimatedTime: this.calculateTime(from, to),
11 waypoints: this.generateWaypoints(from, to),
12 };
13 }
14}
15
16// Kontroler forum - używa inżyniera
17@Controller('forum')
18export class ForumController {
19 constructor(
20 private readonly engineeringService: EngineeringService, // Automatyczne wstrzyknięcie
21 ) {}
22
23 @Post('march-to')
24 setMarch(@Body() destination: Coordinates) {
25 const route = this.engineeringService.calculateRoute(
26 this.getCurrentPosition(),
27 destination
28 );
29 return {
30 message: 'Campaign route set, consul!',
31 route,
32 };
33 }
34}

Wstrzykiwanie z tokenami

Czasem potrzebujemy być bardziej precyzyjni w określaniu, czego potrzebujemy:

1// Definiujemy tokeny dla różnych typów konfiguracji
2export const IMPERIUM_CONFIG = 'IMPERIUM_CONFIG';
3export const ARMORY_CONFIG = 'ARMORY_CONFIG';
4
5// W module definiujemy dostawców z tokenami
6@Module({
7 providers: [
8 {
9 provide: IMPERIUM_CONFIG,
10 useValue: {
11 name: 'Roman Imperium',
12 maxMarchSpeed: 50,
13 legionCapacity: 5000,
14 ballistaCount: 32,
15 },
16 },
17 {
18 provide: ARMORY_CONFIG,
19 useValue: {
20 defaultPila: 100,
21 repairTime: 30,
22 range: 500,
23 },
24 },
25 ForumService,
26 ],
27})
28export class ForumModule {}
29
30// W serwisie używamy tokenów do wstrzyknięcia
31@Injectable()
32export class ForumService {
33 constructor(
34 @Inject(IMPERIUM_CONFIG) private imperiumConfig: any,
35 @Inject(ARMORY_CONFIG) private armoryConfig: any,
36 ) {}
37
38 getImperiumInfo() {
39 return {
40 ...this.imperiumConfig,
41 armoryStatus: this.armoryConfig,
42 };
43 }
44}

Factory Providers - produkcja w Imperium

Czasem musimy stworzyć skomplikowane obiekty:

1// Factory do tworzenia różnych typów broni
2const armoryFactory = {
3 provide: 'ARMORY_FACTORY',
4 useFactory: (config: any, logger: Logger) => {
5 return (type: string) => {
6 switch (type) {
7 case 'ballista':
8 return new Ballista(config.ballistaConfig, logger);
9 case 'pilum':
10 return new Pilum(config.pilumConfig, logger);
11 case 'gladius':
12 return new Gladius(config.gladiusConfig);
13 default:
14 throw new Error(`Unknown weapon type: ${type}`);
15 }
16 };
17 },
18 inject: [ARMORY_CONFIG, Logger],
19};
20
21@Module({
22 providers: [armoryFactory],
23 exports: ['ARMORY_FACTORY'],
24})
25export class ArmoryModule {}

Async Providers - ładowanie zasobów

Niektóre zasoby muszą być załadowane asynchronicznie:

1// Async provider do łączenia z mapą prowincji
2const provinceMapProvider = {
3 provide: 'PROVINCE_MAP_CONNECTION',
4 useFactory: async (): Promise<ProvinceMapConnection> => {
5 const connection = new ProvinceMapConnection();
6 await connection.connect('https://roman-province-maps.com');
7 await connection.authenticate(process.env.IMPERIUM_API_KEY);
8 return connection;
9 },
10};
11
12@Module({
13 providers: [provinceMapProvider],
14 exports: ['PROVINCE_MAP_CONNECTION'],
15})
16export class ProvinceMapModule {}

Zakresy dostawców (Provider Scopes)

Request Scope - każda kampania osobno

1// Serwis logowania dla każdego żądania osobno
2@Injectable({ scope: Scope.REQUEST })
3export class RequestLoggerService {
4 private requestId: string;
5
6 constructor(@Inject(REQUEST) private request: Request) {
7 this.requestId = Math.random().toString(36).substr(2, 9);
8 }
9
10 log(message: string) {
11 console.log(`[${this.requestId}] ${message}`);
12 }
13}

Transient Scope - jednorazowe narzędzia

1// Każde wstrzyknięcie tworzy nową instancję
2@Injectable({ scope: Scope.TRANSIENT })
3export class CampaignCalculatorService {
4 calculateDamage(attackPower: number, defense: number): number {
5 // Każda kampania to nowe obliczenia
6 const randomFactor = Math.random() * 0.2 + 0.9; // 90-110% base damage
7 return Math.max(1, Math.floor((attackPower - defense) * randomFactor));
8 }
9}

Moduły dynamiczne - struktury na zamówienie

Czasem potrzebujemy tworzyć moduły z różną konfiguracją:

1// Dynamiczny moduł bazy danych
2@Module({})
3export class DatabaseModule {
4 static forRoot(config: DatabaseConfig): DynamicModule {
5 return {
6 module: DatabaseModule,
7 providers: [
8 {
9 provide: 'DATABASE_CONNECTION',
10 useFactory: () => createConnection(config),
11 },
12 DatabaseService,
13 ],
14 exports: ['DATABASE_CONNECTION', DatabaseService],
15 global: true,
16 };
17 }
18
19 static forFeature(entities: any[]): DynamicModule {
20 return {
21 module: DatabaseModule,
22 providers: entities.map(entity => ({
23 provide: `${entity.name}_REPOSITORY`,
24 useFactory: (connection: any) => connection.getRepository(entity),
25 inject: ['DATABASE_CONNECTION'],
26 })),
27 exports: entities.map(entity => `${entity.name}_REPOSITORY`),
28 };
29 }
30}
31
32// Użycie w głównym module
33@Module({
34 imports: [
35 DatabaseModule.forRoot({
36 host: 'localhost',
37 port: 5432,
38 database: 'roman_tributes',
39 }),
40 DatabaseModule.forFeature([Legionary, Tribute, Forum]),
41 ],
42})
43export class AppModule {}

Przykład pełnej organizacji legionu

1// Główny moduł aplikacji
2@Module({
3 imports: [
4 // Konfiguracja podstawowa
5 ConfigModule.forRoot({
6 isGlobal: true,
7 envFilePath: '.env',
8 }),
9
10 // Baza danych
11 DatabaseModule.forRoot(databaseConfig),
12
13 // Moduły funkcjonalne
14 LegionModule,
15 TributesModule,
16 CampaignModule,
17 EngineeringModule,
18
19 // Moduły wspólne
20 CommunicationModule,
21 LoggingModule,
22 ],
23 controllers: [AppController],
24 providers: [
25 AppService,
26 // Global guards, interceptors, filters
27 {
28 provide: APP_GUARD,
29 useClass: AuthGuard,
30 },
31 {
32 provide: APP_INTERCEPTOR,
33 useClass: LoggingInterceptor,
34 },
35 ],
36})
37export class AppModule implements NestModule {
38 configure(consumer: MiddlewareConsumer) {
39 consumer
40 .apply(ImperiumAuthMiddleware)
41 .forRoutes('*');
42 }
43}

Najlepsze praktyki organizacji

  1. Jeden cel na moduł - każdy moduł ma jasno określone zadanie
  2. Minimalne eksporty - eksportuj tylko to, co naprawdę potrzebne
  3. Luźne powiązania - moduły powinny być możliwie niezależne
  4. Hierarchiczna struktura - od ogólnych do szczegółowych
  5. Nazewnictwo - jasne nazwy opisujące przeznaczenie
  6. Dokumentacja - każdy moduł powinien mieć opis

Dobra organizacja modułów to fundament skalowalnej aplikacji NestJS. Tak jak dobrze zorganizowany legion potrafi pokonać każdą kampanię, tak przemyślana architektura modułów pomoże ci zbudować aplikację, która będzie rosła razem z twoimi potrzebami!

W następnym module poznamy szczegóły kontrolerów - naszych centurionów, którzy przyjmują rozkazy z zewnątrz i zarządzają operacjami!

Przejdź do CodeWorlds