Makrokorzyści we frontendzie dzięki mikroserwisom


Czym jest podejście mikrofrontendowe?

Historycznie, strony internetowe tworzone były jako monolityczne rozwiązania, serwowane w całości jako odpowiedź HTML z serwera. Wraz z rozwojem technologii – zarówno po stronie serwera jak i przeglądarki, oraz z wzrostem zapotrzebowania na możliwości interakcji użytkownika ze stronami jak z aplikacjami natywnymi, popularność zyskały rozwiązania Single Page Application.

W podejściu SPA większość zagadnień związanych z wyświetlaniem czy działaniem strony przeniesiona została na stronę przeglądarki, a miejsce pojedynczych widgetów, wzbogacających strony, zajęły frameworki które generują całą jej zawartość. Takie podejście, o ile wygodne jest dla developera – pozwala na użycie narzędzia, które oferuje już rozwiązania najczęstszych problemów, o tyle stwarza potencjalne problemy z utrzymaniem i rozwijaniem aplikacji na przestrzeni lat.

Obecnie wciąż dosyć popularnymi są aplikacje stworzone np. w technologii AngularJS, a o developerów znających – lub chcących pracować – w tej technologii trudno. Rozwiązaniem może być podzielenie aplikacji na mniejsze fragmenty, które skupiają się na pojedynczych funkcjach biznesowych i tworzenie ich w sposób pozwalający na komunikowanie i uzupełnianie się.

Benefity stosowania mikrofrontendów

Odpowiednie zastosowanie mikroserwisów pozwala na osiągnięcie wielu celów: od zapewnienia stabilności aplikacji do skrócenia czasu potrzebnego na jej stworzenie.

Każda z części aplikacji może być rozwijana przez niezależny zespół, który jednocześnie staje się właścicielem tego fragmentu. Zespół ten, złożony z przedstawicieli różnych dziedzin – developerów, testerów, specjalistów od baz danych – jest w stanie dokładnie zaznajomić się z wymaganiami i przedstawić działające rozwiązanie w skróconym czasie. Nie musi on komunikować się i ustalać architektury tak, by nie kolidowała ona z pozostałymi częściami aplikacji – wystarczy, że będzie on tworzył swoje rozwiązania w oparciu o ogólny zarys (np. w zakresie doboru technologii) czy standard kodowania. Udostępnia on swój “feature” w postaci określonego API, które gwarantuje, że będzie on wykorzystany zgodnie z przeznaczeniem.

Praca nad częściami aplikacji w takim podejściu przebiega także wielowątkowo – zespoły pracują nad poszczególnymi częściami aplikacji i nie są blokowane do czasu, aż poprzednia funkcjonalność zostanie ukończona. Przez większość czasu development odbywa się także w izolacji – developerzy nie muszą uruchamiać pełnego środowiska na swoich maszynach, skupiają się na testowaniu tylko swojej funkcjonalności (pozostała część testowana jest przez zespół odpowiedzialny za koordynację serwisów).

Również odpowiednie zaplanowanie podziału aplikacji pozwoli na uniknięcie problemów z jej dostępnością w przypadku awarii pojedynczej części. Awaria mniej istotnej funkcjonalności (np. listy polecanych produktów względem aktualnie oglądanego) nie wiąże się z niedostępnością całej aplikacji.

Ostatnim benefitem – na który warto zwrócić uwagę – jest umożliwienie developerom testowania i wdrażania nowych technologii, co pozwala zmniejszyć ryzyko “wypalenia się” developerów. Pozwala to także na dostosowanie oferty do zmieniającego się rynku – jeżeli nasza aplikacja jest stabilna, ale wykonana w nieatrakcyjnej (np. przestarzałej) technologii możemy zaimplementować nową funkcjonalność w innej technologi, bez konieczności migracji podstawowej aplikacji do nowszej wersji.

Mechanizmy implementacji

Istnieje kilka mechanizmów implementacji podejścia mikrofrontendowego – każda z nich ma mocne i słabe strony, a wybór powinien być świadomą decyzją na początku implementacji.

Server Side Include / Edge Side Include

Najstarszą z technik, używaną jeszcze przed utarciem się określenia “mikrofrontend” jest generowanie kodu HTML po stronie serwera za pomocą języka dynamicznego. Najpopularniejszym rozwiązaniem jest tutaj niewątpliwie PHP, które za pomocą metod takich jak include może zaczytywać kod z wielu oddzielnych plików i wysyłać go do użytkownika w odpowiedzi na jedno zapytanie.

Podobnie sprawa ma się w przypadku Edge Side Include – specyfikacji określającej możliwości modyfikowania danych zwracanych przez serwery cache tj. CluodFlare czy Akamai.

Oba te podejścia doskonale sprawdzają się w przypadku często odwiedzanych, ale mało dynamicznych (nie oferujących dużej interaktywności) stron – możemy w ten sposób dołączać do strony dane dla konkretnego użytkownika (zapytania), ale nie stworzymy strony z dynamicznym interfejsem reagującym na działania użytkownika.

Iframe

Kolejnym znanym podejściem jest wykorzystanie ramek – iframe – do ładowania wielu stron w jednej. Każda załadowana strona może posiadać jedną lub więcej aplikacji SPA. W podejściu tym istnieje zawsze aplikacja nadrzędna, która odpowiada za odkrywanie (eng. discovery) wszystkich dostępnych mini-aplikacji i koordynuje ich pracę (np. zapewnia ładowanie zasobów, routing czy współdzielenie danych).

Podejście takie ma kilka zalet:

  • oferuje nam pełną izolację JavaScript i CSS, dzięki czemu unikamy problemów, kiedy jedna i druga aplikacja definiują te same metody lub klasy CSS, ale oczekują od nich innego działania
  • uzyskujemy całkowitą niezależność jeżeli chodzi o wypuszczanie nowych wersji aplikacji – tak długo jak host wie w jaki sposób załadować aplikację, nie musi wiedzieć jaką wersję i z którego release używa. 

Niestety, nie obejdzie się też bez wad:

  • zmiana adresu iframe powoduje utracenie stanu aplikacji, zarówno danych jak i wyglądu interfejsu – oczywiście możemy tutaj utworzyć aplikację data-driven, gdzie cały stan przechowujemy w hoście i w momencie powrotu do aplikacji zasilamy ją tym stanem; alternatywnie zamiast usuwać iframe możemy ukrywać go w CSS
  • iframe ma także swój własny bagaż: problemy z accessibility, problemy z SEO, problemy z prezentacją (np. okno modalowe otwarte w iframe nie ma możliwości zasłonięcia UI znajdującej się poza modalem)

multi-spa

Podejście, w którym na jednej stronie używamy jednocześnie kilku różnych mini aplikacji (wykonanych w tej samej lub innych technologiach) jest podejściem dającym nam dużo większy wachlarz możliwości, ale jednocześnie wymagającym większej dyscypliny i dokładniejszego planowania.

Podobnie jak w przypadku stosowania iframe i w tym momencie potrzebujemy aplikację host, która zarządza ładowaniem i uruchamianiem poszczególnych aplikacji, ale inaczej niż w tamtym przypadku aplikacje te renderowane są do pojedynczej struktury HTML.

Jako zalety tego zastosowania na pewno zaliczyć można:

  • niezależne releasy
  • łatwą możliwość współdzielenia stanu – jeżeli nasze aplikacje korzystają z zewnętrznego magazynu danych (np. redux) wystarczy, że uczynimy go globalnym w naszym hoście (np. window.store = redux.createStore()) a pozostałe aplikacje stworzymy tak, by “oczekiwały” że obiekt ten istnieje 

Patrząc na popularne wady, możemy dojść do wniosku że nie jest to odpowiednie rozwiązanie, ale każdą z nich można szybko skorygować:

  • wielokrotne ładowanie tego samego kodu (np. biblioteki frameworku) na potrzeby kilku aplikacji powoduje zwiększenie transferu dla użytkownika – możemy zastosować tutaj rozwiązania takie jak DllPlugin dla WebPacka dzięki czemu wszystkie współdzielone zależności znajdą się w jednym pliku; następnie plik ten możemy wysłać użytkownikowi np. na ekranie logowania, gdzie i tak spędzi on kilka minut wpisując swoje hasło
  • konflikty klas CSS – tutaj sprawę możemy rozwiązać albo proponując wykorzystanie współdzielonej biblioteki UI, albo stosując np. css-modules, dzięki czemu nasze klasy zostaną zamienione na pseudo-losowe
  • aktualizacja wersji zależności jednej z aplikacji może wymagać aktualizacji innej aplikacji – problem ten (szczególnie dokuczliwy jeżeli zastosowaliśmy rozwiązanie z pierwszej uwagi) możemy rozwiązać stosując podejście ładowania modułów ESM na poziomie przeglądarki a nie podczas budowania aplikacji

Web Components

Ostatnim z rozwiązań o których chciałem wspomnieć to Web Components. Jest to rozwiązanie pozwalające na definiowanie własnych komponentów poprzez opisanie ich wyglądu (HTML+CSS) i działania (JS) i “zarejestrowanie” ich jako komponenty obsługiwane przez przeglądarki. Po tej operacji możemy stosować je jak “zwykłe” tagi HTML – nie musimy żadnych dodatkowych bibliotek i ryzykować ich niekompatybilności.

Jeżeli założymy, że naszym rynkiem są tylko nowe przeglądarki (czyli nie wspieramy IE11) zyskujemy pakiet korzyści:

  • natywne wsparcie w przeglądarce, dzięki czemu komponenty są mniejsze i szybciej wykonywane
  • izolacja CSS gwarantowana jest przez sam standard web components
  • raz zadeklarowany komponent możemy wyświetlić poprzez osadzenie jego tagu w DOMie – oznacza to, że komponenty mogą być renderowane zarówno przez inne komponenty jak i przez frameworki typu React, Angular etc.

Na dzień dzisiejszy do minusów musimy zaliczyć jednak:

  • brak wsparcia dla server-side-rendering – jeżeli zależy nam na tym, by nasza aplikacja wyglądała poprawnie dla użytkowników nie posiadających obsługi JavaScript (w tym dla robotów większości wyszukiwarek czy parserów Open Graph) musimy dostarczyć dodatkowy plik CSS
  • jest to wciąż stosunkowo mało popularne i młode rozwiązanie, co przekłada się na brak sprawdzonych rozwiązań, stosunkowo aktywne zmiany kierunku i najlepszych praktyk

Jak wygląda przyszłość mikrofrontendów?

Jeżeli spojrzymy na tech radar wskazujący technologie, którym warto się przyjrzeć (w.g. firmy Thought Works, niejako odpowiedzialnych za popularyzację tego wzorca), mikrofrontendy znajdują się w samym centrum sekcji “zaadoptuj”.

Technologia ta na pewno będzie rozwijać się i ewoluować przez najbliższe lata i na pewno jest warta rozważenia wszędzie tam, gdzie skupiamy się na tworzeniu oprogramowania, które zastępuje istniejącą, sprawdzoną technologię (i nie mamy czasu lub budżetu na całkowite jej przepisanie) lub będzie musiało być wspierane przez lata.