Przejdź do treści

DevStyle - Strona Główna
Architektura frontendowa: czym jest a czym nie?

Architektura frontendowa: czym jest a czym nie?

Tomasz Ducin

18 września 2025

Pierwsza część wpisu tutaj.

Wróćmy do mojej szybkiej i uproszczonej definicji architektury:

(Ważne, techniczne) decyzje, które podejmujesz na podstawie wymagań biznesowych, które kształtują Twój system już dziś i które trudno będzie zmienić w przyszłości.

Teraz skonfrontujmy, co spełnia tę definicję, a co nie:

Architektura określona przez:

✅ Decyzję, czy implementować MicroFrontendy (MFEs), czy nie (i jak)
❌ A nie to, czy używamy webpack module federation czy czegoś innego

Architektura określona przez:

✅ Czy ważniejsza jest reużywalność, czy izolacja – albo odwrotnie
❌ A nie to, czy w kodzie stosujemy barrel files (index.js/ts), czy coś innego

Architektura określona przez:

✅ Czy modele danych są współdzielone między modułami, czy izolowane (np. przez ACL)
❌ A nie to, czy trzymasz się klas/OOP czy funkcji/FP

Architektura określona przez:

✅ Czy stan ma być scentralizowany/współdzielony, czy rozproszony/lokalny
❌ A nie to, czy do zarządzania stanem używamy czegoś reduxopodobnego

Architektura określona przez:

✅ Czy model PULL czy PUSH lepiej odpowiada naszym potrzebom
❌ A nie to, czy korzystamy z promises, async await, rxjs czy czegokolwiek innego

Architektura określona przez:

✅ Czy nasz interfejs mocno opiera się na danych w czasie rzeczywistym
❌ A nie to, czy korzystamy z firebase czy supabase, czy […]

Architektura określona przez:

✅ Kto może zmieniać kontrakt API między klientem a serwerem?
❌ A nie to, czy korzystamy z GraphQL, REST, SSE itd.

Architektura określona przez:

✅ Które drivery architektoniczne są w naszym przypadku najważniejsze?
❌ A nie to, czy twierdzimy, że „stosujemy best practices”

Architektura określona przez:

✅ Czy mocno zoptymalizowane LCP to „nice-to-have” czy „must-have”
❌ A nie to, jak duże są nasze komponenty UI (w linijkach kodu)

Architektura określona przez:

✅ Czy system jest jedno- czy wielotenantowy (single or multi-tenant)?
❌ A nie to, czy dane logowania trzymamy w Reduxie, kontekście, useState…

Architektura określona przez:

✅ Jak zapewniamy odporność UI na błędy (fault tolerance)?
❌ A nie to, czy pipeline CI wymaga 80% pokrycia testami, inaczej się wywala

Na tym etapie mam nadzieję, że już dobrze rozumiesz, o co chodzi 😅.

Zwróć też uwagę, że powyższe zestawienia przeciwstawiają decyzje architektoniczne decyzjom technicznym.

Dlaczego struktura katalogów nie jest architekturą?

Struktura katalogów to efekt pewnych decyzji projektowych i przyjętych konwencji. Ich celem jest ułatwienie pracy: szybsze poruszanie się po kodzie, sprawniejsze dostarczanie funkcjonalności, minimalizowanie ryzyka wprowadzania błędów.

Ale sama struktura katalogów nie jest celem samym w sobie. To jedynie środek do osiągnięcia wyższych, bardziej abstrakcyjnych celów, takich jak:

  • oddzielenie bounded contexts MFE/Modulith
  • umożliwienie niezależnym zespołom dostarczanie i wdrażanie niezależnie od siebie nawzajem

  • wspieranie izolacji lokalnych modeli poprzez ACL itp.

Jak wyraźnie widać, struktura katalogów może lepiej lub gorzej pasować do danej architektury. Jednakże jest ona materializacją koncepcji, implementacją. Szczegółem implementacyjnym. Jest sposobem na osiągnięcie naszego ostatecznego celu.

Czego struktura katalogów nam nie powie

Istnieje mnóstwo istotnych aspektów, których nie jesteśmy w stanie wywnioskować z samej struktury katalogów czy przyjętych konwencji. Na przykład:

Delikatnie mówiąc, struktura katalogów nie jest na tyle istotna, by uznać ją za architekturę. Może ona wspierać przyjętą architekturę (lub nie), ale sama w sobie architekturą nie jest.

Budowanie właściwego rozumienia architektury frontendowej

Architektura to nie coś, czego pragniemy, pożądamy 🥰 czy narzucamy siłą 😤. To nie coś, co wyciągamy nagle znikąd 😶‍🌫️. I z pewnością nie coś, co powinniśmy kopiować i wklejać między projektami, tylko dlatego, że działało dobrze w poprzedniej firmie.

Architektura jest wynikiem komunikacji, dogłębnej analizy i przemyśleń. To rezultat. Tak, jak funkcja, która dla dla określonych danych wejściowych produkuje określone wyjście. I, na pewno, z czasem, dane wejściowe będą się zmieniać. Rolą architekta jest zdobycie wiedzy i doświadczenia, aby regularnie uruchamiać tę funkcję input -> output.

Jakie są dane wejściowe i skąd je pozyskać?

Najważniejszą umiejętnością architekta jest komunikacja, ze wszystkimi: menedżmentem, biznesem i zespołami developerskimi. To zupełnie różne światy, prawda? Trzeba zebrać ogromną ilość informacji, między innymi:

  • Co oferuje produkt? Jakie są jego przewagi? Co stanowi core domain, który daje nam przewagę nad konkurencją?

  • Moduły/funkcjonalności/zespoły będące częścią core domain nie powinny być outsourcowane osobom trzecim

  • Jakość nie może być poświęcana

  • Odkrywanie/modelowanie domeny może być prowadzone w oparciu o praktyki DDD, takie jak Event Storming

  • Domeny nie-core’owe są drugorzędne

  • Struktura organizacyjna firmy i wpływ prawa Conwaya na sposób tworzenia i dostarczania oprogramowania

  • Jak duży jest dział developerski? Ile mamy zespołów?

  • Czy firma jest zorientowana produktowo? Czy każdy zespół bierze pełną odpowiedzialność za (sub)produkt, który samodzielnie rozwija, testuje, wdraża itp. 100% niezależnie od innych zespołów?

  • Czy są spółki powiązane, współdzielone technologie, przejęcia lub inne krytyczne czynniki wpływające na naszą organizację teraz lub w niedalekiej przyszłości, które mogą unieważnić nasze dzisiejsze założenia co do „jak strukturyzować zespoły”?

  • Cele biznesowe, oczekiwania klientów, sytuacja rynkowa
  • Na kiedy oczekiwane są dostarczenia rozwiązań? Czy mamy know-how, by je osiągnąć?
  • Jak często powinny odbywać się wdrożenia/dostarczenia? Czy firma jest na to gotowa?

Przejdźmy do aspektów rozwoju

Na poziomie technicznym formułujemy następujące pytania:

  • Na ile powinniśmy promować lub ograniczać ponowne użycie kodu?

  • Im więcej kodu współdzielimy, tym mniej go musimy pisać od nowa, ale też tym mniej niezależne stają się zespoły (!), zwłaszcza gdy współdzielone fragmenty zmieniają się częściej niż rzadziej (w porównaniu do architektury „Shared Nothing”)
  • Jakie będą konsekwencje zmiany współdzielonego elementu (pliku, biblioteki komponentów, artefaktu itp.)? Czy będzie konieczna ponowna kompilacja? Jeśli tak — ilu elementów to dotyczy? Ile testów (miejmy nadzieję, że automatycznych) trzeba będzie przeprowadzić? Ile komponentów musi zostać wdrożonych? Sekwencyjnie czy niezależnie? Ile czasu to wszystko zajmie? Ile niepotrzebnego wysiłku lub straty czasu wprowadza takie współdzielenie?
  • A jak to wpływa na nasz uptime/SLA?
  • Jak elastyczny jest Twój system i jak mierzysz jego architekturę?
  • Innymi słowy, jaki jest oczekiwany oraz aktualny TTM? Ale również:
  • Jak często zmiany w aplikacji są wdrażane na produkcję (DF)?
  • Jak długo trwa proces od momentu, gdy programista rozpoczyna kodowanie, do chwili, gdy zmiana trafia na produkcję (CLT)?
  • Jak często wdrożone zmiany powodują awarie (CFR)?
  • Ile czasu zajmuje przywrócenie systemu do pełnej sprawności po awarii (MTTR/FDRT)?
  • Jak zapewniasz stabilność systemu (poza testami, pokryciem kodu, progami itp.):
    • Jaka jest procedura w przypadku awarii? Jak często zespoły muszą z niej korzystać? 😄

    • Ile/jak duże komponenty muszą być wdrażane synchronicznie/wspólnie?
      Czy wdrożenie Twojego produktu wymaga niepotrzebnych buildów zależności tylko z powodu struktury CI/CD i (nadmiernie rozdrobnionych) repozytoriów?

    • Jak bardzo zespoły ufają sobie nawzajem?
      Czy warto zakładać, że to, co dostarczają, jest w porządku? Czy stosujecie workflow w stylu Git-flow, czy rozwój oparty na głównej gałęzi (trunk-based development)? CI/CD i cały proces będą w ogromnym stopniu zależeć od tych decyzji.

    • Na ile odporny na błędy jest system?
      Czy aplikacja frontendowa jest automatycznie testowana pod kątem różnych scenariuszy awarii backendu?

    • Czy deweloperzy mogą skutecznie korzystać z narzędzi do obserwowalności?
      Na przykład, jak długo zajmuje:

      • Zdiagnozowanie źródła problemu w frontendzie?

      • Wycofanie zmian (rollback)?

      • Wprowadzenie poprawki?

      • Lub jak/kiedy deweloperzy zauważają regresję w Core Web Vitals?

  • Jakie konkretne urządzenia/środowiska wykorzystują nasi użytkownicy? Web? Aplikacje mobilne (natywne)? A może oba?  Jeszcze raz:
  • które komponenty warto współdzielić, a które lepiej rozdzielić (forkować), aby zminimalizować zależności między zespołami?
  • jak zorganizowane są zespoły/bazy kodu? Według platform czy bounded contexts? 😉 itp.
  • Jakie konkretne cechy są oczekiwane/wymagane?

  • przykład: tryb współpracy. Obecnie system pozwala na wprowadzanie zmian przez jednego użytkownika, ale wymaganiem jest umożliwienie wielu użytkownikom jednoczesnego wprowadzania zmian w czasie rzeczywistym na tym samym zbiorze danych. Jeśli Twój kod dokonuje bezpośrednich zmian stanu (np. set, update itp.), może brakować wygodnego modelu do współdzielenia między użytkownikami. Ale jeśli zmiany stanu odbywają się pośrednio (np. za pomocą wzorca Command, np. poprzez akcje Redux), to już masz model, który można współdzielić w pierwszej iteracji (wprowadzania funkcji współpracy). Albo sięgnij po CRDTs.
  • przykład: nigdy nie pokazuj nieaktualnych danych. Jeśli widzisz nieaktualną liczbę polubień pod swoim postem, nikt nie ucierpi. Okej, miejmy nadzieję, ale wiesz, o co chodzi. 😉 Jednak w systemie bankowym nie jest dobrym pomysłem pozwalanie interfejsowi użytkownika na wyświetlanie nieaktualnego salda konta, np. podczas przełączania się między zakładkami/sekcjami.
  • przykład: SDK. Czy Twoi klienci implementują własne funkcje na bazie udostępnionej przez Ciebie platformy? Jak często wprowadzasz breaking changes (i jak często Twoi klienci to przeklinają)? Jak znaleźć równowagę między rozwojem systemu a nieponoszeniem ogromnych kosztów związanych z utrzymaniem starszych wersji przy ograniczonych zasobach? Na przykład, przeprojektowanie propsów komponentu React dla kilku atomów może mieć naprawdę ogromne konsekwencje i, mimo że zostało zaimplementowane, przetestowane itp., może – niestety – zostać cofnięte z powodu wymogu non-breaking changes.

Jak widać, zarządzanie architekturą to przede wszystkim sztuka zadawania właściwych pytań. Jak mawiał klasyk:

Wolę mieć pytania, na które nie da się odpowiedzieć, niż odpowiedzi, których nie wolno kwestionować.

Przemyślenie wszystkich powyższych kwestii powinno doprowadzić nas do podjęcia decyzji dotyczącej stylu architektonicznego oraz narzędzi, których używamy.

Decyzje dotyczące narzędzi a decyzje dotyczące architektury

Niektórzy mogą powiedzieć:

Hej, stary, ale są pewne decyzje związane z narzędziami, bibliotekami, konwencjami lub strukturą kodu, które mogą mieć znaczący wpływ na projekt i które trudno później zmienić. Na przykład, jeśli używam Redux, to jest to decyzja architektoniczna!!!1 😉

Cóż, tak i nie. 😉

Dość łatwo jest trzymać się pewnych słów i stracić kontekst tego, co jest ważne (tj. ważne z biznesowego i/lub ogólnego punktu widzenia).

To prawda, że decyzja o konwencji kodowania może być (bardzo) kosztowna do zmiany po kilku latach. Konwencja kodowania, struktura kodu lub struktura katalogów (naprawdę, cokolwiek) może przyspieszyć lub spowolnić programistów – oczywiście! Niektóre konwencje będą pasować bardziej, inne mniej. Niektóre pomagają nam działać szybciej, inne nie itd.

Ale kiedy oddalisz się od zespołu, w którym podjęto decyzję, to po prostu NIE MA ZNACZENIA 🔥. To szczegół implementacji. Nie ma znaczenia poza zespołem.

  • Przykład: zdecydujesz się zachować tylko jeden komponent na plik. Całkowicie nieistotne poza zespołem.
  • Przykład: decydujesz się na użycie lodash lub ramda dla helperów lub nie używasz żadnej biblioteki helperów, ponieważ nie została ona wynaleziona tutaj (czy cokolwiek innego). Nadal nieistotne poza zespołem.
  • Przykład: wprowadzasz bardzo specyficzną strukturę plików dla każdego dostarczanego modułu. Konwencja wpływa na testowanie, storybook i refaktoryzację. Nadal nieistotne poza zespołem. (BTW, storybook miałby znaczenie, gdyby był regularnie używany poza zespołem).

Proszę, nie zrozum mnie źle. Te decyzje mają znaczenie. Są ważne dla zespołu. Ale TYLKO dla zespołu. Nie mają one wpływu na ogólną charakterystykę systemu. Gdyby decyzja była inna, ogólna charakterystyka systemu nie uległaby zmianie. Przeanalizujmy dalej powyższy cytat:

Jeśli używam Redux, jest to decyzja architektoniczna.

(sory, Redux 😅)

Uwaga: decyzja architektoniczna nie dotyczy wyboru samego Reduxa! Chodzi o wybór scentralizowanego rozwiązania do zarządzania stanem, ponieważ może to pozwolić modułom na wzajemną zależność (każdy ma dostęp do wszystkiego w globalnym storage, prawda?), Lub w przypadku dystrybucji monolitu na mikrofrontendy – zadanie to byłoby łatwiejsze dzięki wielu oddzielnym storage-om (takim jak mobx). Ponadto decyzja architektoniczna dotyczy wyboru rozwiązania do pozyskiwania zdarzeń po stronie klienta, ponieważ firma może wymagać wdrożenia funkcji współpracy w czasie rzeczywistym.

Czy wybór Redux niesie za sobą konsekwencje? Oczywiście. Ale ponownie, nie chodzi o samą bibliotekę, na której chcę się skupić – chodzi o wysokopoziomowe cechy, które wnosi Redux. Zarówno możliwości, które przynosi (wspomniane kilka razy wcześniej), jak i koszty i ograniczenia we wprowadzaniu, np. Redux jest JEDNYM i JEDYNYM pojedynczym źródłem prawdy, co zdecydowanie nie jest dobrą rzeczą, jeśli kiedykolwiek rozważasz mikrofrontendy. Redux jest nierozerwalnie związany ze swoimi cechami. Ale to cechy budują architekturę, a nie same narzędzia.

Weźmy jeszcze jeden, tym razem z ekosystemu Angular:

Nie zgadzam się, tu chodzi przede wszystkim o samą bibliotekę, zwłaszcza jeśli mówimy o rozwiązaniach wyższego poziomu, takich jak NgRx. Istnieje wiele pytań, na które należy odpowiedzieć:

  • Jak korzystamy z NGRX?

  • Czy zawsze stosujemy efekty (effects)?

  • Czy abstrahujemy je za pomocą fasady (facade)?

  • Z którymi warstwami je łączymy?

  • Jak dzielimy store NGRX między domenami? itd.

Więc przejdźmy po kolei:

Jak korzystamy z NGRX?

To trudne pytanie, ponieważ „jak” może odnosić się zarówno do aspektów wysokopoziomowych, jak i niskopoziomowych. Niejednoznaczne pytanie 😉

Czy zawsze stosujemy efekty (effects)?

(kontekst: NGRX effects to ta sama koncepcja, co redux-observable’s epics: akcje są wysyłane, a następnie przetwarzane reaktywnie za pomocą reaktywnych strumieni rxjs, a często skutkują pochodnymi nowymi akcjami wysyłanymi z powrotem do storage).

To szczegół implementacji. Niezależnie od tego, czy wybierzemy paradygmat imperatywny czy reaktywny, jest to paradygmat programowania (implementacji). Nie architektura. Możemy zmienić tę decyzję później, tj. robić rzeczy inaczej w przyszłości.

Czy abstrahujemy je za pomocą fasady (facade)?

To enkapsulacja i/lub wzorce projektowe i/lub wzorce kodowania… jeden poziom poniżej wzorców architektonicznych. W modelu C4 jest to kod (poziom 4) (szczegóły implementacji). Ponownie – czy jest to ważne w zespole? TAK. Czy jest to ważne na zewnątrz? Nie.

Z którymi warstwami je łączymy?

Jasne, potencjalnie architektura – ale nie ma to nic wspólnego z NGRX (czy czymkolwiek innym). Można by zadać to samo pytanie w przypadku innych rozwiązań do zarządzania stanem, takich jak: ile / jak konkretnych niestandardowych haków w React powinniśmy stworzyć. Hipotetyczne warstwy (lub ich brak) niewątpliwie kształtują naszą architekturę. Ale czy nie robiłyby tego również, gdybyśmy używali innych bibliotek?

Jak dzielimy store NGRX między domenami?

Absolutnie, zdecydowanie architektura. Ale znowu, nie ma to nic wspólnego z samym NGRX, ponieważ można zadać dokładnie to samo pytanie w przypadku wszystkich innych scentralizowanych rozwiązań do zarządzania stanem. Prawda?

Na marginesie: to, czy używasz NGRX/redux-observables, czy nie, z pewnością wpływa na poziom wejścia programistów frontendowych, wpływa na ich motywację (związek miłości z nienawiścią do narzędzi 🥹), z pewnością wpływa na sposób pisania testów i tak dalej, i tak dalej. Ale znowu, jeśli wyjdziesz poza swój zespół/moduł/repo, to czy to naprawdę ma tak duże znaczenie?

Podsumowując, to, czy zmiana decyzji jest kosztowna, czy nie, nie determinuje, czy jest ona w jakikolwiek sposób istotna w szerszej perspektywie i / lub długoterminowo. Ponadto to, co jest bardzo ważne lokalnie w zespole/repo, nie determinuje, czy jest to istotne na zewnątrz. Może, ale nie musi.

Moim skromnym zdaniem nie ma większego znaczenia, czy nazwiemy wybór Redux decyzją architektoniczną, czy nie, o ile skupimy się na konsekwencjach tej decyzji.

Podsumowanie

Architektura polega na podejmowaniu ważnych decyzji. Powinny one wynikać z priorytetów biznesowych, należy brać pod uwagę kompromisy, a istniejące ograniczenia sprawiają, że jest to jeszcze trudniejsze.

W obliczu tych wszystkich trudności, rolą architekta jest balansowanie pomiędzy priorytetami biznesowymi i wymaganiami – a aspektami technicznymi i ich złożonością.

Nie należy mylić architektury (wysokopoziomowych decyzji, które mają pomóc w osiągnięciu celu) ze sposobem jej wdrożenia. Określone narzędzia, biblioteki, konwencje, interfejsy API itp. – to wszystko są niskopoziomowe szczegóły, które mogą przybliżyć Cię do osiągnięcia celu lub nie. Ale z perspektywy priorytetów biznesowych, ograniczeń (i tak dalej) są to tylko szczegóły. Szczegóły o niewielkim znaczeniu.

Mam nadzieję, że się podobało, dzięki za przeczytanie 🤓.

Specjalne podziękowania dla Damiana, Mateusza i Manfreda za cenny feedback.

Polecane książki

Istnieje kilka naprawdę dobrych książek na ten temat, ale zanim zagłębisz się w bardziej szczegółowe tematy, polecam zacząć od podstaw:

Ponadto na stronie Martina Fowlera znajdziesz dobre wprowadzenie do wielu aspektów architektury.

Tomasz Ducin – niezależny konsultant, architekt i deweloper. Prelegent na europejskich i polskich konferencjach. Trener z pasją do wyjaśniania działania rzeczy i unikania zbyt skomplikowanych rozwiązań. Krytyk buzzwordów i nadmiernej ekscytacji niektórymi narzędziami. Skoncentrowany na rozwiązywaniu problemów organizacyjnych i technicznych projektów. Uwielbia pracę z ludźmi. Były aktor teatralny.

Skontaktuj się z Tomaszem, aby uzyskać informacje na temat szkoleń z JavaScript, TypeScript, React, Angular i architektury.

Obserwuj Tomasza na bluesky , LinkedIn i X

Zobacz również