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

Moduły i Dependency Injection

Masz już kontroler i serwis. Ale skąd NestJS wie, że ten kontroler istnieje? I skąd bierze serwis, który wstrzykuje mu do konstruktora? Same klasy w plikach nic nie znaczą - ktoś musi je zgłosić.

Imperium nie zarządzało prowincjami z jednego biurka. Każda miała własną administrację: swój spis urzędów, swoich specjalistów i wykaz tego, co udostępnia sąsiadom. Cesarz znał tylko listę prowincji, nie każdego pisarza z osobna. W NestJS taką prowincją jest moduł.

Dekorator @Module

Moduł to klasa z dekoratorem, który wylicza jej zawartość:

1import { Module } from '@nestjs/common';
2
3@Module({
4  imports: [DatabaseModule],
5  controllers: [LegionController],
6  providers: [LegionService],
7  exports: [LegionService],
8})
9export class LegionModule {}

Sam import dekoratora ma stałą postać -

import
,
{ Module }
,
from
,
'@nestjs/common'
.

Cztery pola opisują całą prowincję i każde odpowiada na inne pytanie:

controllers
- kto przyjmuje żądania.
providers
- jacy specjaliści tu pracują; to tu rejestrujesz serwisy.
imports
- z których innych modułów korzystamy.
exports
- co udostępniamy na zewnątrz.

To są wszystkie pola dekoratora

@Module
. Nie ma wśród nich pola
routes
- trasy nie są konfigurowane w module, tylko wynikają z dekoratorów w kontrolerze, które poznałeś wcześniej.

exports - co widać zza granicy

Pole

exports
jest tym, które najczęściej sprawia kłopot, więc nazwijmy je wprost: serwis wypisany w
exports
będzie dostępny dla modułów, które importują ten moduł
.

Co z tego wynika w drugą stronę - serwis zarejestrowany w

providers
, ale nieobecny w
exports
, działa tylko wewnątrz swojego modułu. Inny moduł, który spróbuje go wstrzyknąć, dostanie przy starcie błąd o nierozwiązanej zależności. To jest właśnie ten błąd, który pojawia się w połowie pierwszych projektów NestJS.

Zwróć uwagę, czego

exports
nie robi: nie usuwa serwisu z modułu, nie zmienia go w kontroler i nie ma nic wspólnego z zapisem do bazy. To wyłącznie deklaracja widoczności.

Domyślna zamkniętość jest celowa. Moduł ma jawnie powiedzieć, co udostępnia - dzięki temu widać granice, a przypadkowe zależności między odległymi częściami aplikacji nie powstają same z siebie.

Cztery kroki wstrzykiwania

Skoro moduł rejestruje providerów, możemy złożyć całą drogę zależności. Ma cztery kroki, zawsze w tej kolejności:

  1. Zdefiniowanie providera z
    @Injectable()
    - klasa gotowa do wstrzyknięcia.
  2. Rejestracja w tablicy
    providers
    modułu - NestJS dowiaduje się, że taka klasa istnieje.
  3. Wstrzyknięcie przez konstruktor tam, gdzie jest potrzebna.
  4. Użycie serwisu w metodach klasy.

Krok drugi jest tym, o którym łatwo zapomnieć przy dodawaniu nowego serwisu: sam dekorator

@Injectable()
nie wystarczy. NestJS buduje mapę zależności z tego, co wypisano w modułach, a nie ze skanowania plików.

Moduł globalny

Niektóre rzeczy przydają się wszędzie - konfiguracja, logger, połączenie z bazą. Importowanie ich modułu w każdym innym module jest wtedy tylko powtarzaniem:

1@Global()
2@Module({
3  providers: [ConfigService],
4  exports: [ConfigService],
5})
6export class ConfigModule {}

Dekorator

@Global()
sprawia, że moduł jest dostępny bez importowania. Uwaga na nazwę - nie ma dekoratorów
@Shared()
,
@Public()
ani
@Universal()
.

Jedna rzecz nie zmienia się mimo globalności:

exports
nadal obowiązuje.
@Global()
zwalnia z importowania modułu, ale to
exports
decyduje, co ten moduł w ogóle udostępnia.

Używaj go oszczędnie, @name. Moduł globalny znika z listy importów, więc przestaje być widać, kto od czego zależy - a to jest informacja, która ratuje przy większym projekcie. Konfiguracja i logger tak; wszystko inne raczej nie.

Provider pod nazwą

Do tej pory providerów rozpoznawaliśmy po typie klasy. Ale wstrzyknąć można też zwykłą wartość - obiekt, tablicę, liczbę - a te typu nie mają:

1@Module({
2  providers: [
3    {
4      provide: 'CONFIG_TOKEN',
5      useValue: { apiUrl: 'https://api.imperium.rome', timeout: 5000 },
6    },
7  ],
8})
9export class AppModule {}

useValue
podaje gotową wartość zamiast klasy do utworzenia, a
provide
nadaje jej nazwę - token, po którym będzie rozpoznawana.

Skoro nie ma tu typu, po którym NestJS mógłby dobrać provider, przy wstrzykiwaniu wskazujemy token wprost:

1@Injectable()
2export class ApiService {
3  constructor(@Inject('CONFIG_TOKEN') private config: { apiUrl: string }) {}
4}

@Inject('CONFIG_TOKEN')
mówi: podaj mi to, co zarejestrowano pod tą nazwą. Przy zwykłych serwisach dekorator jest zbędny, bo wystarcza typ - tutaj jest konieczny.

Podsumowanie

Prowincje mają swoje administracje, cesarz zna tylko ich listę:

  • moduł grupuje powiązane elementy i zgłasza je NestJS - same klasy w plikach nic nie znaczą,
  • import dekoratora:
    import
    ,
    { Module }
    ,
    from
    ,
    '@nestjs/common'
    ,
  • cztery pola
    @Module
    :
    controllers
    ,
    providers
    ,
    imports
    ,
    exports
    - pola
    routes
    nie ma
    , trasy wynikają z dekoratorów kontrolera,
  • serwis w
    exports
    jest dostępny dla modułów importujących ten moduł
    ; bez tego działa tylko wewnętrznie,
  • exports
    niczego nie usuwa ani nie zmienia - to sama deklaracja widoczności,
  • cztery kroki wstrzykiwania:
    @Injectable()
    → rejestracja w
    providers
    → wstrzyknięcie przez konstruktor → użycie w metodach
    ,
  • sam
    @Injectable()
    nie wystarczy - NestJS buduje mapę zależności z modułów, nie ze skanowania plików,
  • @Global()
    udostępnia moduł bez importowania
    ;
    @Shared()
    ,
    @Public()
    i
    @Universal()
    nie istnieją,
  • globalność nie zwalnia z
    exports
    - i stosuj ją oszczędnie, bo ukrywa zależności,
  • useValue
    rejestruje gotową wartość,
    provide
    nadaje jej token, a
    @Inject('TOKEN')
    wskazuje go przy wstrzykiwaniu.

W następnej lekcji poznasz kontrolery od środka - zobaczysz, jak żądanie trafia do właściwej metody. A na razie zapamiętaj: moduł to administracja prowincji - rejestruje, kto tu pracuje, i jawnie mówi, czego użyczy sąsiadom.

Przejdź do CodeWorlds