Blog
30.10.2020
KP

Angular  - architektura projektu to podstawa

Angular - architektura projektu to podstawa Łukasz Miedziński Zastosowanie odpowiedniej architektury do rozwiązywanego problemu tak, aby później nasz system był skalowalny i łatwy w rozwoju zawsze...

Angular  - architektura projektu to podstawa

Łukasz Miedziński

 

Zastosowanie odpowiedniej architektury do rozwiązywanego problemu tak, aby później nasz system był skalowalny i łatwy w rozwoju zawsze jest pewnym wyzwaniem. Kiedy chcemy to osiągnąć w projekcie Front-End, sprawy komplikują się często jeszcze bardziej nawet jeśli wybieramy framework tak bardzo „narzucający” porządek w kodzie jakim jest Angular. Dzisiejsze aplikacje SPA (Single Page Application) nie służą tylko do wyświetlania i przyjmowania prostych danych tak jak kiedyś, teraz większość logiki obsługiwana jest po stronie użytkownika. Musimy liczyć się z coraz to nowszymi i bardziej rozwiniętymi regułami biznesowymi, zwiększającą się ilością danych oraz jednoczesną pracą kilku zespołów nad różnymi częściami systemu. Jak więc możemy maksymalnie przygotować się na takie zdarzenia? Stosując dobry podział na wysokopoziomowe warstwy, wydzielając osobne moduły dla poszczególnych funkcjonalności oraz rozróżniając komponenty proste od złożonych – wszystko dlatego, że „architektura projektu to podstawa”. 

 

Podział na wysokopoziomowe warstwy

Pierwszym krokiem do poprawy architektury jest podział aplikacji na trzy wysokopoziomowe warstwy – pozwoli to jasno rozdzielić odpowiedzialności dla poszczególnych części projektu. Takie rozwiązanie usprawnia późniejsze zmiany w kodzie poprzez likwidowanie zależności w losowych miejscach i zastępowanie ich uporządkowanymi ścieżkami komunikacji.
 

 

Warstwa prezentacji (Presentation layer)

Odpowiedzialność tej warstwy ogranicza się wyłącznie do wyświetlania danych i delegowania dalej ewentualnych akcji wykonanych przez użytkownika. Wszystkie Angular’owe komponenty znajdują się bezpośrednio w warstwie prezentacji.

 

Warstwa abstrakcji (Abstraction layer)

Wprowadzenie abstrakcji ma za zadanie połączyć ze sobą prezentację i logikę – dla usprawnienia późniejszych zmian w projekcie, nie chcemy żeby te warstwy były ze sobą ściśle (bezpośrednio) związane. W tym celu możemy utworzyć pewnego rodzaju fasady, czyli klasy oznaczone adnotacją „@Injectable()”, które udostępnią interfejs komunikacji dla komponentów wykorzystując przy tym w swojej implementacji metody z warstwy logiki. Warto zaznaczyć, że nie powinno się tu pojawić nic związanego z regułami biznesowymi ani z samym sposobem przechowywania stanu aplikacji.

To co dodatkowo może obsługiwać warstwa abstrakcji to połączenie ze sobą elementów warstwy logiki – na przykład przekazywanie pobranych z API danych do serwisu zarządzającego stanem aplikacji.

Po stronie fasady zostaje jeszcze odpowiedzialność za wybranie czy aktualizacja danych ma się odbywać w optymistyczny czy pesymistyczny sposób. Pierwszy zakłada zmianę stanu zanim otrzymamy potwierdzenie wykonania operacji z API i ewentualną korektę w przypadku błędu. To podejście jest proste i szybkie w działaniu dla użytkownika niezależnie od prędkości jego łącza internetowego ale może powodować zakłopotanie jeżeli po chwili w wyniku błędu dane zostaną skorygowane, a użytkownik zobaczy komunikat o niepowodzeniu operacji. Podejście pesymistyczne może spowalniać działania użytkownika, gdyż zwykle operacjom w interfejsie aplikacji towarzyszą animacje ładowania ale za to stan aplikacji jest od razu „pewny” i zmienia się dopiero po uzyskaniu odpowiedzi z serwera.

Ostatnim zadaniem, które można powierzyć klasie fasadowej, to cache danych pobranych z zewnętrznego API. Jeżeli dane są tylko do odczytu, często nie ma potrzeby przetrzymywania ich wewnątrz stanu aplikacji.

 

Warstwa logiki (Core layer)

Na koniec została warstwa logiki. To tutaj odbywa się cała komunikacja ze światem zewnętrznym, przeprowadzane są operacje na danych, a przede wszystkim zaimplementowane są reguły biznesowe. Do celów odpytywania API powinno się używać serwisów, które wywołują zadane endpointy przy okazji unikając cache’owania lub manipulacji danymi. Same dane do edycji, dobrze jest przechowywać razem ze stanem aplikacji. Niezależnie czy wykorzystujemy do tego rozwiązania takie jak NgRx czy zwykłe BehaviorSubject, wszystkie klasy z tym związane należą również do warstwy logiki.

 

Podział na moduły

Skoro wiemy już jak podzielić aplikację na warstwy, to przejdziemy do podziału pionowego. Jego głównym celem jest oddzielenie od siebie części kodu, które różnią się od siebie pod względem funkcjonalności biznesowej. Tak jak w poprzednich przypadkach ułatwi to późniejsze utrzymanie projektu ale również zapewni możliwość zarządzania ładowaniem przez przeglądarkę internetową poszczególnych modułów funkcjonalnych według rzeczywistego zapotrzebowania. W przypadku aplikacji Front-End, czas ładowania odgrywa znaczącą rolę – szczególnie przy słabym łączu internetowym. Właśnie z tego powodu możemy zdecydować, które moduły będą ładowane od razu (te najczęściej odwiedzane), a które zostaną pobrane dopiero przy chęci skorzystania z ich funkcjonalności (te rzadziej odwiedzane).

Oprócz wspomnianych wyżej modułów funkcjonalnych, w każdej aplikacji Angularowej tworzony jest główny AppModule ale do dobrej architektury brakuje nam jeszcze dwóch podstawowych modułów. Jako pierwszy powinniśmy utworzyć CoreModule, który będzie zawierał konfigurację oraz serwisy o jednej instancji (singleton). Drugim podstawowym modułem do utworzenia jest SharedModule. Ma on grupować wszystkie często używane komponenty, dyrektywy czy pipe, które potem będzie można wygodnie zaimportować do każdego modułu funkcjonalnego.

 

Podział na komponenty proste i złożone

Opisane wcześniej rady zaczęły się od wysokiego poziomu (warstwy), następnie weszły głębiej (moduły), więc teraz pozostało wspomnieć o niskopoziomowym aspekcie – komponentach. Zazwyczaj nie zastanawiamy się jak duży wpływ na architekturę całej aplikacji ma ich odpowiednie wydzielanie. Warto jednak poświęcić chwilę i wyróżnić dwa typy komponentów: proste (dumb) i złożone (smart).

Komponenty proste powinny charakteryzować się (uwaga, niespodzianka!) prostotą. Zwykle są to elementy interfejsu, które wyświetlają dane przekazane przez parametr wejściowy i propagują akcje użytkownika wyżej poprzez zdarzenia. Dobrym przykładem może być formularz tworzenia lub edycji konta użytkownika. Niezależnie od sytuacji będą to te same pola, a dopiero samą akcję zatwierdzenia potrzebujemy obsłużyć na różne sposoby.

Tutaj wkraczają do gry komponenty złożone, które posiadają wstrzyknięte fasady. Będąc pewnego rodzaju kontenerami na komponenty proste, mogą koordynować ich wykorzystanie i delegować komunikację do wyższych warstw.

 

Mam nadzieję, że te proste w zastosowaniu porady pomogą kiedyś przy opracowywaniu architektury dla projektów Front-End w technologii Angular.

Zobacz też...