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

Architektura NestJS - budowa Imperium

Pierwszy projekt zwykle mieści się w jednym pliku. Drugi ma ich pięć. Przy dwudziestym nikt już nie wie, gdzie leży obliczanie żołdu - w kontrolerze, w jakimś pomocniku, czy może w trzech miejscach naraz, każde nieco inaczej.

Rzym nie rozrósł się przez dosypywanie ludzi do jednej kohorty. Rozrósł się, bo każdy miał jedną rolę i wszyscy wiedzieli, która to: posłaniec nosi rozkazy, kwatermistrz liczy zapasy, pisarz prowadzi rejestr. NestJS narzuca ten sam podział - i właśnie ta narzucona struktura sprawia, że dwudziesty plik nadal da się znaleźć.

Wzorzec, na którym to stoi

Podstawą architektury NestJS jest Model-View-Controller (MVC) - wzorzec dzielący aplikację na trzy odpowiedzialności: dane, ich prezentację i obsługę żądań.

Nie myl go z wzorcami, które rozwiązują zupełnie inne problemy: Singleton pilnuje, by istniała jedna instancja klasy, Observer rozsyła powiadomienia o zdarzeniach, Strategy pozwala wymieniać algorytm w trakcie działania. Wszystkie trzy spotkasz wewnątrz NestJS, ale to MVC wyznacza kształt całej aplikacji.

W backendzie warstwa View zwykle znika - odpowiedzią jest JSON, nie strona HTML - więc na co dzień pracujesz z dwiema pozostałymi i z modułem, który je spina.

Trzy role

Cała aplikacja NestJS składa się z trzech powtarzalnych elementów. Warto je zapamiętać przez pytanie, na które każdy odpowiada:

  • Kontroler - kto przyjmuje żądanie? Obsługuje żądania HTTP i zwraca odpowiedzi. Nie przechowuje danych, nie zajmuje się wyglądem, nie kompiluje niczego.
  • Serwis - jak to zrobić? Tu mieszka logika.
  • Moduł - co należy do siebie? Grupuje jedno i drugie i zgłasza NestJS.

Serwis - gdzie mieszka logika

Zacznijmy od środka, bo to serwis niesie właściwą wartość:

1@Injectable()
2export class LegionService {
3  private legions = [
4    { id: 1, name: 'Legio X Equestris' },
5    { id: 2, name: 'Legio III Gallica' },
6  ];
7
8  findAll() {
9    return this.legions;
10  }
11}

Kolejność deklaracji jest stała:

@Injectable()
, potem
export class
, dalej nazwa klasy, na końcu ciało z metodami.

I tu pada odpowiedź na pytanie, które w praktyce rozstrzyga o jakości całego projektu: logika biznesowa należy do serwisów - tego, co NestJS nazywa providerami. Nie do kontrolerów, nie do middleware, nie do plików konfiguracyjnych.

Powód jest prosty. Logika w kontrolerze jest przywiązana do HTTP - nie wywołasz jej z zadania wsadowego ani z testu jednostkowego bez udawania żądania. W middleware jest jeszcze gorzej, bo middleware pracuje na surowym żądaniu, zanim wiadomo, dokąd ono trafi. Logika w serwisie jest zwykłą metodą zwykłej klasy - wywołasz ją skąd chcesz.

Kontroler - kto przyjmuje żądanie

Kontroler bierze serwis przez konstruktor i deleguje mu pracę:

1@Controller('legions')
2export class LegionController {
3  constructor(private readonly legionService: LegionService) {}
4
5  @Get()
6  findAll() {
7    return this.legionService.findAll();
8  }
9}

Zwróć uwagę na proporcje. Metoda kontrolera ma jedną linię - i tak ma być. Cała jej rola to przyjąć żądanie i wskazać wykonawcę.

Kontroler nie tworzy serwisu przez

new
. Wpisuje go w konstruktor, a NestJS podaje gotowy - to wstrzykiwanie zależności, mechanizm, który poznasz dokładniej w kolejnych lekcjach. Na razie wystarczy widzieć skutek: kontroler mówi, czego potrzebuje, i nie martwi się, skąd to wziąć.

Moduł - co należy do siebie

Trzeci element spina dwa poprzednie:

1@Module({
2  controllers: [LegionController],
3  providers: [LegionService],
4})
5export class LegionModule {}

Bez tego zgłoszenia NestJS nie wie, że te klasy istnieją - pliki same z siebie nic nie znaczą.

Zauważ, że w module widać całą prowincję naraz: kto przyjmuje żądania i kto wykonuje pracę. Otwierając nieznany projekt, moduły czyta się pierwsze, bo to one pokazują mapę.

Droga żądania

Złóżmy to w jeden przebieg. Żądanie GET /legions wędruje tak: trafia do kontrolera

LegionController
, ten wywołuje metodę serwisu
LegionService
, serwis zwraca dane, kontroler oddaje je jako odpowiedź. Moduł nie bierze udziału w locie - zadziałał wcześniej, przy starcie, gdy NestJS budował z niego mapę zależności.

Ten schemat powtarza się w całej aplikacji, niezależnie od jej wielkości. Dwudziesty zasób wygląda tak samo jak pierwszy - i dlatego wiadomo, gdzie szukać obliczania żołdu, @name: w serwisie, zawsze w serwisie.

Podsumowanie

Imperium ma administrację, każdy zna swoją rolę:

  • podstawą architektury NestJS jest Model-View-Controller (MVC) - nie Singleton, nie Observer, nie Strategy,
  • w backendzie warstwa View zwykle znika, bo odpowiedzią jest JSON,
  • kontroler obsługuje żądania HTTP i zwraca odpowiedzi - nie przechowuje danych ani nie zajmuje się wyglądem,
  • logika biznesowa należy do serwisów (providerów) - nie do kontrolerów, middleware ani plików konfiguracyjnych,
  • powód: logika w serwisie jest zwykłą metodą, więc wywołasz ją także poza HTTP,
  • deklaracja serwisu w kolejności:
    @Injectable()
    ,
    export class
    , nazwa, ciało z metodami,
  • kontroler przyjmuje serwis przez konstruktor, nie tworzy go przez
    new
    ,
  • metoda kontrolera ma zwykle jedną linię - przyjmij i deleguj,
  • moduł zgłasza kontrolery i providery; bez tego NestJS ich nie widzi,
  • droga żądania: kontroler → serwis → dane → odpowiedź; moduł działa wcześniej, przy starcie.

W następnej lekcji przyjrzymy się modułom dokładniej - zobaczysz, jak dzielą aplikację na prowincje i co decyduje o tym, że jedna widzi drugą. A na razie zapamiętaj: kontroler przyjmuje, serwis wykonuje, moduł rejestruje - i ten sam układ powtarza się w każdym zakątku aplikacji.

Przejdź do CodeWorlds