Powrót do strony głównej

Frontend jako serwer REST: architektura i wzorce

Artykuł bada architektoniczne podobieństwa między rozwojem klient-side a serwer-side. Autor pokazuje, jak stosowanie wzorców REST, DTO i architektury warstwowej we frontendzie zmniejsza sprzężenie kodu i ułatwia utrzymanie SPA.

Klient REST: porządkujemy architekturę frontendu
Advertisement 728x90

# Frontend jako klientowski serwer REST: dyscyplina architektoniczna zamiast chaosu menedżerów stanu

Współczesna development frontendowa dawno przekroczyła etap manipulacji DOM i prostej weryfikacji. Pod względem architektury aplikacje SPA i SSR wykonują tę samą task, co klasyczne systemy serwerowe: przyjmują przychodzące sygnały, przetwarzają dane zgodnie z regułami logiki biznesowej i zwracają gotowy widok. Różnica polega jedynie na środowisku wykonania i protokole interakcji. Zamiast nieustannego wymyślania nowych abstrakcji pod każdy release frameworka, efektywniej zastosować sprawdzone wzorce serwerowe bezpośrednio do kodu klientowego.

Ewolucja roli: od hipermedii do renderowania po stronie klienta

Początkowo web działał według przejrzystego schematu: przeglądarka żądała URL, serwer generował HTML, klient wyświetlał wynik. Niesamowite, ale standardowy HTML jest natywną implementacją zasady HATEOAS. Serwer zwraca nie tylko dane, ale stan interfejsu wraz z dostępnymi przejściami: linkami, formularzami z określonymi metodami, przyciskami akcji. Klient nie przechowuje mapy tras z góry — reaguje na to, co jest dozwolone w bieżącym kontekście.

Współczesne podejście z JSON API jedynie zmieniło format przekazywania stanu, ale nie jego istotę. Tę samą zasadę można wyrazić za pomocą formatu HAL:

Google AdInline article slot
{
  "id": 42,
  "status": "pending",
  "_links": {
    "self":   { "href": "/orders/42" },
    "cancel": { "href": "/orders/42/cancel", "method": "POST" },
    "detail": { "href": "/orders/42/detail" }
  }
}

Wzór UI = f(state) istniał na długo przed pojawieniem się wirtualnego DOM. Wcześniej stan był przekazywany z zdalnego serwera w całości, teraz jest obliczany lokalnie i aktualizowany przyrostowo. Problem nie powstał ze zmianą paradygmatu, lecz z utratą dyscypliny architektonicznej przy przenoszeniu logiki na klienta.

Wydarzenia jako żądania: mapowanie warstw architektonicznych

W developmentce backendowej interakcja jest ściśle uregulowana. Kontroler przyjmuje żądanie HTTP, weryfikuje dane wejściowe, przekształca je w DTO i deleguje przetwarzanie do warstwy serwisowej. Serwis pracuje z modelem domenowym, a repozytorium odpowiada za trwałość. Granice odpowiedzialności są wyraźne i nie zależą od konkretnego języka czy frameworka.

We frontendzie działania użytkownika (kliknięcia, wprowadzanie, przewijanie, wiadomości z WebSocket) pełnią rolę pełnego odpowiednika żądań HTTP. Event = Request. Jednak zamiast ujednoliconych kontrolerów ekosystem wytworzył zoo terminów: store'y, composable, hooki, atomy, runy, view-modelle. Każdy narzędzie narzuca swoją filozofię zarządzania stanem, rozmywając granice architektoniczne. Logiczniej dostosować architekturę klientową do standardu serwerowego:

Google AdInline article slot
Frontend
  
  웃  →  Event   → Controller → Service → Repository  → → → → → → Rest API
  │     │         │            ↑         │                               │
  │     │         │            │         └─ Builds HTTP Request          │
  │     │         │            │                                         │
  │     │         │            └─ Business logic                         │
  │     │         │                                                      │
  │     │         └─ Extracts, validates & maps to DTO                   │
  │     │                                                                │
  │     └─ Environment detail - Browser Event                            │
  │                                                                      │
  │                                                                      │
  │                                                                      │
  └─  ← Response ← Controller ← Service ← Repository  ← ← ← ← ← ← Rest API
        │          │                      │
        │          │                      └─ Maps to Domain Model
        │          │                             
        │          └─ Maps to framework format: JSX/Vue template
        │
        └─ Automatically mapped to HTML chunk from JSX by framework

Kontrolery z stanem i szczegóły infrastruktury

Główne zastrzeżenie wobec bezpośredniego przenoszenia wzorców serwerowych dotyczy stanu. Klasyczne kontrolery REST są projektowane jako bezstanowe dla poziomego skalowania. Frontend z definicji działa w kontekście stateful: sesja użytkownika, lokalny cache, stan formularzy i elementów UI żyją w pamięci przeglądarki. To nie sprzeczność, lecz cecha środowiska. W backendzie podobne wzorce stateful stosuje się w gamedevie, bramkach IoT i serwisach streamingowych opartych na gRPC lub WebSockets.

Hooki, sygnały i reaktywne atomy należy traktować wyłącznie jako szczegóły infrastruktury. To mechanizmy, które framework wykorzystuje do efektywnej aktualizacji DOM i śledzenia zależności. Błąd polega na tym, że programiści zaczynają przechowywać reguły biznesowe i logikę walidacji bezpośrednio w tych prymitywach. Menedżery stanu często maskują przeciekające abstrakcje, pozwalając komponentom UI bezpośrednio zależeć od struktury magazynu. Wyodrębnienie warstwy kontrolerów i serwisów rozwiązuje ten problem, izolując logikę biznesową od reaktywnego systemu frameworka.

Praktyczne zastosowanie dyscypliny serwerowej

Wdrożenie podejść backendowych nie wymaga rezygnacji z React, Vue czy Solid. Chodzi o strukturyzację kodu wewnątrz ekosystemu. Dla osiągnięcia przewidywalności i zmniejszenia obciążenia poznawczego przy onboardingu nowych programistów wystarczy przestrzegać kilku zasad:

Google AdInline article slot
  • Ścisłą walidację przychodzących wydarzeń i danych z API na granicy aplikacji (użycie Zod, Yup lub strażników TypeScript).
  • Przekształcanie surowych odpowiedzi serwera i danych wejściowych użytkownika w DTO przed przekazaniem do warstwy biznesowej.
  • Izolację logiki domenowej w czystych serwisach, które nie importują hooków frameworka i nie wiedzą o istnieniu DOM.
  • Użycie repozytoriów do abstrakcji nad źródłami danych (LocalStorage, IndexedDB, REST, GraphQL).
  • Kontrolery pełnią rolę jedynego punktu połączenia: mapują wydarzenia na wywołania serwisów, a wyniki — na propsy dla komponentów prezentacyjnych.

Takie podejście czyni kod agnostycznym wobec frameworków. Logika biznesowa łatwo pokrywa się testami jednostkowymi bez mocków renderera, a migracja z jednego rozwiązania UI na inne dotyka tylko cienkiej warstwy adapterów.

Co ważne

  • Aplikacja frontendowa architektonicznie jest równoważna serwerowi REST rozłożonemu w przeglądarce: wydarzenia zastępują żądania HTTP, a renderowanie pełni rolę formowania odpowiedzi.
  • HTML historycznie realizuje zasady HATEOAS, przekazując klientowi stan i dostępne akcje jednocześnie.
  • Różnorodność terminów (store'y, composable, atomy) tworzy iluzję nowości, ale często rozmywa granice odpowiedzialności i komplikuje utrzymanie kodu.
  • Hooki i sygnały to infrastrukturalne prymitywy frameworka, a nie miejsce na przechowywanie reguł biznesowych czy skomplikowanej walidacji.
  • Stosowanie dyscypliny serwerowej (DTO, kontrolery, serwisy, repozytoria) zmniejsza sprzężenie kodu, upraszcza testowanie i czyni architekturę niezależną od konkretnego frameworka UI.

— Editorial Team

Advertisement 728x90

Czytaj dalej