Wróć do strony głównej
NestJS

Jak postępować zgodnie z Zasadą Odwrócenia Zależności (DIP) w NestJS i Angular

dokumentacji NestJS możemy znaleźć następujące zdanie:

“Nest provides an out-of-the-box application architecture which allows developers and teams to create highly testable, scalable, loosely coupled, and easily maintainable applications”

Przetestujmy tą tezę przez sprawdzenie czy NestJS pozwala na łatwe postępowanie zgodnie z jedną z zasad SOLID – z Zasadą Odwrócenia Zależności!

NestJS jest silnie inspirowany Angularem, dlatego wszystkie rady z tego artykułu działają także w Angular.

Dla szybkiego przypomnienia, Zasada Odwrócenia Zależności mówi że:

  1. Wysokopoziomowe moduły nie powinny zależeć od niskopoziomowych modułów. Powinny zależeć od ich abstrakcji (np. interfejsów).
  2. Abstrakcja nie powinna zależeć od szczegółów. Szczegóły (konkretna implementacja) powinna zależeć od abstrakcji.

Podczas implementacji DIP będziemy korzystać z wbudowanego w NestJS i Angular mechanizmu Wstrzykiwania Zależności (DI). Jest on kluczowy, aby w naszym wysokopoziomowym module otrzymać odpowiedni serwis niższego poziomu. Nie będziemy się jednak dokładnie zagłębiać w sposób działania DI, a jedynie skupimy się na tym jak wykorzystać go w implementacji DIP.

Przykład

Aby wyjaśnić sposób implementacji DIP rozważmy najpierw następujący przykład.
Tworzymy aplikację, która będzie analizować dane o naszych repozytoriach na platformie Github. Naszym aktualnym zadaniem jest zaimplementowanie endpointa, który będzie zwracał liczbę aktywnych pull requestów dla danego repozytorium.

Podaną funkcjonalność można szybko zaimplementować w przedstawiony poniżej sposób, jednak nie jest ona zgodny z naszą zasadą.

Nawet przeniesienie zapytania HTTP do dedykowanego serwisu nie jest wystarczającą drogą, aby ukryć implementację przed kontrolerem.

Implementacja DIP

Prawidłowa implementacja powinna wyglądać następująco:

  1. Musimy stworzyć warstwę abstrakcji dla pobierania kolekcji pull requestów. Ta abstrakcja jest osiągnięta przez klasę abstrakcyjną RepositoryService.
  2. Do AppControler próbujemy wstrzyknąć coś ukrytego za injection tokenem RepositoryService 
  3. GithubInfrastructureModule mówi nam, że w to miejsce powinna być wstrzyknięta klasa GithubRepositoryService.

Ok, ale dlaczego nie używamy zwykłego interfejsu jako warstwa abstrakcji?
Odpowiedzią jest to co się dzieje z interfejsami podczas procesu transpilacji z TypeScript do JavaScript. Problemem jest to, że tracimy wszystkie informacje o istnieniu interfejsu RepositoryService, o tym co powinno być wstrzyknięte do AppController. Właściwie to nie można użyć interfejsu jako wartość pola “provide” przy definiowaniu dostawców modułu.
Sytuacja jest zupełnie inna jeśli użyjemy klasy. Nawet klasa abstrakcyjna po transpilacji pozostanie zwykłą klasą, która może  zostać użyta jako Injection Token. W Typescript klasa może implementować inną klasę tak samo jak interfejs.

Dlaczego to jest takie istotne?

Powiedzmy że wymagania naszej aplikacji się zmieniły. Musimy także wspierać repozytoria na platformie Bitbucket na dedykowanej instancji aplikacji.

Jeśli nie przygotowalibyśmy się na to wcześniej, teraz musielibyśmy dodać wiele warunków w naszych serwisach i kontrolerach, aby wykonać odpowiednie zapytania HTTP po potrzebne dane.

Z elegancko ukrytą warstwą dostępu do danych możemy po prostu stworzyć dedykowany moduł z serwisami dla Bitbucketa i odpowiednia zaimportować go w module z naszą funkcjonalnością:

Teraz do AppController zostanie wstrzyknięty GithubRepositoryService lub BitbucketRepositoryService zależnie od środowiska.

Dobrymi przykładami gdzie ma to swoje zastosowanie w aplikacji napisanej w Angular są sytuacje gdy:

  • tworzymy aplikację webową oraz mobilną i wyróżniają się sposobem pobierania danych
  • tworzymy aplikację z SSR i przy pobieraniu danych po stronie serwera chcielibyśmy dodatkowo ustawić je w TransferState, a po stronie przeglądarki chcemy je stamtąd pobrać.

W obu tych przypadkach postępowanie zgodnie z Zasadą Odwrócenia Zależność tak jak było to opisane powyżej pomoże nam w implementacji i ograniczy zmiany oraz logikę w wysokopoziomowych modułach, które wykorzystują naszą warstwę dostępu do danych.

Podsumowanie

Postępowanie zgodnie z Zasadami SOLID daje nam mnóstwo korzyści. Powoduje że nasza aplikacja staje się łatwiej reużywalna, utrzymywalna, skalowalna, testowalna i wiele więcej. NestJS i Angular pozwalają nam na używanie ich w łatwy i elegancki sposób.

Jeśli interesuje Cię sposób w jaki możemy wykorzystywać GithubRepositoryService i BitbukcetRepositoryService w tej samej instancji aplikacji, zajrzyj tutaj.

Reasumując, tezę z dokumentacji NestJS można uznać za prawdziwą!

O autorze

Maciej Sikorski

Maciej najwięcej czasu spędza projektując aplikacje w Angular i w Node.js, ale poza tym tworzy i wspiera biblioteki oraz inne narzędzia, które wykorzystuje w codziennej pracy. Jest bardzo zaangażowany w rozwój społeczności i technologii związanej z frameworkiem NestJS. Maciek dodatkowo prowadzi bloga anglojęzycznego na medium o tematyce NestJS i Angular.

Zapisz się do naszego newslettera. Bądź na bieżąco z najnowszymi trendami, poradami, meetupami i stań się częścią społeczności Angulara w Polsce. Rynek pracy docenia członków społeczności.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *