Organizacja kodu schematu GraphQL w aplikacji node.js

405
views

W pierwszym wpisie dotyczącym implementacji GraphQL w aplikacji node.js wspominałem, że chciałbym ten temat rozwinąć jeszcze szerzej. Wspominałem też, że jest to pierwsza bazowa implementacja, która z pewnością w przyszłości ewoluuje. I tak też się stało – poświęciłem ostatnio trochę czasu na organizacji kodu podczas deklarowania schematu GraphQL i tym właśnie chciałbym się z wami podzielić w dzisiejszym wpisie.

Jak było na początku?

Przy pierwszej iteracji projektu deklaracja elementów schematu (Query i Mutation) odbywało się w jednym miejscu zbiorczo dla każdego z typów.

Zacznijmy od tego, że pierwotna forma schematu, która została zaprezentowana w poprzednim wpisie nie była najgorsza. Sprawdzała się, bo w aplikacji póki co nie ma wielu akcji do wykonania i nawet jeśli cała deklaracja odbywała się w jednym miejscu, nie było to uciążliwe. Jednak rozbudowujemy sandbox, na którym warto trenować i sprawdzać różne rozwiązania. Popatrzmy na wklejony kod powyżej, dotyczy on deklarowania typu głównego jakim jest Query – wszystko ładowane jest w jednym miejscu i wraz z rozrastaniem się aplikacji wzrasta liczba linii kodu w tym pliku i wszystko staje się coraz mniej czytelne.

Jaki jest cel?

Dobrze by było odwrócić proces komponowania schematu ponieważ pozwoli nam to na lepsze panowanie nad kodem i zwiększy możliwości testowania. Po analizie kilku wariantów na organizację kodu dobrym sposobem wydaje się zastosowanie wzorca kompozyt. Oczywiście nie uda się dokonać tego w całości chociażby z tego względu, że JavaScript nie posiada interfejsów, ale będziemy się wzorować na głównych założeniach tego wzorca.

Rozdzielenie każdego elementu schematu

Z wyżej przedstawionego schematu każde pole (authors, author, books, book) zostaje przeniesione do osobnych klas, których instancje będą dołączane w odpowiednich momentach.

Dla przykładu: powyżej przedstawiona jest definicja zapytania, która pobiera dane autora. Funkcja schema zwraca praktycznie to samo co w pierwotnej wersji. Warto też zauważyć, że funkcja resolvera zostaje dołączona w konstruktorze, co znacznie ułatwi testowanie.

Resolver jako akcja – oderwanie od warstwy aplikacji

Kolejnym klockiem w układance jest stworzenie osobnych akcji. Powinny zostać zaprojektowane w taki sposób aby były całkowicie odseparowane od zasad panujących w GraphQL dzięki czemu zachowane zostaną osobne warstwy aplikacji.

Być może wydaje się to przerost formy nad treścią gdyż funkcja execute nie robi nic spektakularnego, jednak warto myśleć przyszłościowo i mieć na uwadze, że projekty informatyczne ciągle się zmieniają, a jednym z lepszych sposobów, który pozwoli rozwijać aplikację jest rozbijcie odpowiedzialności.

Root type

Czas przejść poziom niżej i umożliwić teraz złączenie wszystkich pól w celu zbudowania schematu. Stworzona została odpowiednia klasa, którą można zobaczyć poniżej.

Jeśli przyjrzeć się temu co zwraca funkcja schema można zauważyć, że jest to dokładnie to samo co zostało przedstawione na 1 listingu jednak w tym wypadku jest sparametryzowana co dodaje większej elastyczności, którą można dostosować odpowiednio zarówno do obiektu typu Query czy Mutation.

Budowanie elementów

Teraz czas złączyć typ Query wraz z jego wszystkimi polami – do tego celu stworzona została fabryka. W tym miejscu następuje też inicjalizacja akcji, które będą wstrzyknięte do elementów Query i wykorzystane jako resolvery.

Podobna fabryka powstaje dla typu Mutation po czym można już przejść do ostatniego kroku budowanie schematu.

Schemat

Stworzone wcześniej fabryki można wykorzystać do ustawienia ostatecznego schematu. Jak widać poniżej jest to teraz niezwykle prosta sprawa 😉

Wywołanie schematu

Posiadając już wszystkie klocki poskładane ostatecznym krokiem jest „powołanie do życia” serwera GraphQL z wykorzystaniem nowo stworzonego schematu. Poniżej wycinek kodu z pliku index.js.

Podsumowanie

W dzisiejszym poście zaprezentowałem propozycję organizacji kodu podczas projektowania schematu GraphQL dzięki któremu można zyskać kilka korzyści:

  • stosowana jest zasada SRP
  • stosowana jest zasada DIP (o ile można tak uznać naciąganie JavaScriptu 😜)
  • łatwość w testowaniu wszystkich elementów w całkowitej separacji
  • rozdzielenie akcji od warstwy API – gdyby GraphQL w jakimś miejscu był złym rozwiązaniem to wystarczy przekierować użycie akcji w inne miejsce bez konieczności jej modyfikacji
  • rozrastająca się aplikacja API nie powinny stać się problemem w tak szybkim czasie jak miałoby to miejsce w pierwotnej wersji

Podobnie jak poprzednio obecna wersja projektu dostępna jest na GitHubie pod konkretnym tagiem, aby zamrozić obecny stan kodu.