Mocne strony i zalety mikro frontendów

Opublikowany: 2022-03-11

Architektura mikro-frontendowa to podejście projektowe, w którym aplikacja front-endowa jest rozkładana na pojedyncze, pół-niezależne „mikroaplikacje” luźno ze sobą współpracujące. Koncepcja mikro-frontendu jest niejasno inspirowana i nazwana od mikroserwisów.

Zalety wzorca micro-frontend obejmują:

  1. Architektury mikrofrontendowe mogą być prostsze, a przez to łatwiejsze do zrozumienia i zarządzania.
  2. Niezależne zespoły programistyczne mogą łatwiej współpracować nad aplikacją typu front-end.
  3. Mogą zapewnić możliwość migracji ze „starej” aplikacji, ponieważ obok niej działa „nowa” aplikacja.

Chociaż mikrofrontendy cieszą się ostatnio dużym zainteresowaniem, jak dotąd nie ma jednej dominującej implementacji ani wyraźnego „najlepszego” frameworka mikrofrontendowego. W rzeczywistości istnieje wiele podejść w zależności od celów i wymagań. Zobacz bibliografię niektórych z bardziej znanych implementacji.

W tym artykule pominiemy większość teorii mikrofrontendów. Oto, czego nie omówimy:

  • „Pokrojenie” aplikacji na mikroaplikacje
  • Problemy z wdrażaniem, w tym dopasowanie mikrofrontendów do modelu CI/CD
  • Testowanie
  • Czy mikroaplikacje powinny być wyrównane jeden-do-jednego z mikrousługami na zapleczu
  • Krytyka koncepcji mikro-frontendu
  • Różnica między mikrofrontendami a zwykłą starą architekturą komponentów

Zamiast tego przedstawimy samouczek dotyczący mikro-frontendu skupiający się na konkretnej implementacji, podkreślający ważne problemy w architekturze mikro-frontendowej i ich możliwe rozwiązania.

Nasza realizacja nazywa się Yumcha. Dosłowne znaczenie słowa „yum cha” w języku kantońskim to „picie herbaty”, ale jego codzienne znaczenie to „wychodzenie na dim sum”. Pomysł polega na tym, że poszczególne mikroaplikacje w makroaplikacji (jak nazwiemy skomponowaną aplikację najwyższego poziomu) są analogiczne do różnych koszy z porcjami wielkości kęsa, które są wydawane podczas lunchu dim sum.

Ilustracja ogólna przykładowej aplikacji opartej na mikrofrontendzie, jak opisano powyżej.

Czasami będziemy odnosić się do Yumchy jako „frameworka mikro-frontendowego”. W dzisiejszym świecie termin „framework” jest zwykle używany w odniesieniu do Angular, React, Vue.js lub innych podobnych nadbudówek dla aplikacji internetowych. W ogóle nie mówimy o ramach w tym sensie. Yumcha nazywamy frameworkiem tylko ze względu na wygodę: w rzeczywistości jest to bardziej zestaw narzędzi i kilka cienkich warstw do tworzenia aplikacji opartych na mikro-frontendzie.

Samouczek dotyczący mikro-frontendu Pierwsze kroki: znaczniki dla skomponowanej aplikacji

Zanurzmy się w tym, jak możemy zdefiniować makroaplikację i mikroaplikacje, które ją tworzą. Znaczniki zawsze były sercem sieci. Nasza macroapp nie będzie więc określona przez nic bardziej skomplikowanego niż ten znacznik:

 <html> <head> <script src="/yumcha.js"></script> </head> <body> <h1>Hello, micro-frontend app.</h1> <!-- HERE ARE THE MICROAPPS! --> <yumcha-portal name="microapp1" src="https://microapp1.example.com"></yumcha-portal> <yumcha-portal name="microapp2" src="https://microapp2.example.com"></yumcha-portal> </body> </html>

Zdefiniowanie naszej makroaplikacji za pomocą znaczników daje nam pełny dostęp do możliwości HTML i CSS w celu rozplanowania i zarządzania naszymi mikroaplikacjami. Na przykład jedna mikroaplikacja może znajdować się na drugiej lub z boku, być w rogu strony, w jednym okienku akordeonu lub pozostać ukryta, dopóki coś się nie wydarzy, lub pozostać na stałe w tle. .

Niestandardowy element używany w mikroaplikacjach nazwaliśmy <yumcha-portal> , ponieważ „portal” jest obiecującym terminem dla mikroaplikacji użytych w propozycji portalu, która jest wczesną próbą zdefiniowania standardowego elementu HTML do użytku w mikrofrontendach.

Implementacja elementu niestandardowego <yumcha-portal>

Jak powinniśmy zaimplementować <yumcha-portal> ? Ponieważ jest to element niestandardowy, oczywiście jako składnik sieciowy! Możemy wybierać spośród wielu silnych konkurentów do pisania i kompilowania komponentów webowych typu micro-frontend; tutaj użyjemy LitElement, najnowszej iteracji projektu Polymer. LitElement obsługuje cukier składniowy oparty na TypeScript, który obsługuje większość elementów niestandardowych. Aby udostępnić <yumcha-portal> na naszej stronie, musimy dołączyć odpowiedni kod jako <script> , tak jak to zrobiliśmy powyżej.

Ale co właściwie robi <yumcha-portal> ? Pierwszym przybliżeniem byłoby po prostu utworzenie iframe z określonym źródłem:

 render() { return html`<iframe src=${this.src}></iframe>`; }

…gdzie render jest standardowym hakiem renderującym LitElement, korzystającym z literału szablonu ze znacznikami html . Ta minimalna funkcjonalność może wystarczyć w niektórych trywialnych przypadkach użycia.

Osadzanie mikroaplikacji w iframe s

Elementy iframe s to element HTML, którego wszyscy uwielbiają nienawidzić, ale w rzeczywistości zapewniają niezwykle przydatne, solidne zachowanie w piaskownicy. Jednak nadal istnieje długa lista problemów związanych z praniem, o których należy pamiętać podczas korzystania z elementów iframe , które mogą mieć potencjalny wpływ na zachowanie i funkcjonalność naszej aplikacji:

  • Po pierwsze, iframe mają dobrze znane dziwactwa, jeśli chodzi o ich rozmiar i układ.
  • CSS będzie oczywiście całkowicie odizolowany od elementu iframe , na dobre lub na złe.
  • Przycisk „wstecz” przeglądarki będzie działał dość dobrze, chociaż aktualny stan nawigacji elementu iframe nie zostanie odzwierciedlony w adresie URL strony , więc nie mogliśmy ani wycinać i wklejać adresów URL, aby uzyskać ten sam stan skomponowanej aplikacji, ani precyzyjnego linku do nich.
  • Komunikacja z iframe z zewnątrz, w zależności od naszej konfiguracji CORS, może wymagać przejścia przez protokół postMessage .
  • Trzeba będzie dokonać uzgodnień dotyczących uwierzytelniania poza granicami iframe .
  • Niektóre czytniki ekranu mogą potykać się o granicę elementu iframe lub potrzebują, aby element iframe miał tytuł, który mogą ogłosić użytkownikowi.

Niektórych z tych problemów można uniknąć lub złagodzić je, nie używając elementów iframe — alternatywy, którą omówimy w dalszej części artykułu.

Plusem jest to, że iframe będzie miał własną, niezależną Content-Security-Policy (CSP). Ponadto, jeśli mikroaplikacja wskazywana przez element iframe korzysta z elementu Service Worker lub implementuje renderowanie po stronie serwera, wszystko będzie działać zgodnie z oczekiwaniami. Możemy również określić różne opcje piaskownicy dla elementu iframe , aby ograniczyć jego możliwości, na przykład możliwość nawigowania do górnej ramki.

Niektóre przeglądarki dostarczyły lub planują dostarczyć atrybut loading=lazy dla iframe , który odracza ładowanie ramek iframe poniżej części przewinięcia, dopóki użytkownik nie przewinie ich w pobliżu, ale nie zapewnia to precyzyjnej kontroli nad leniwym ładowaniem. chcemy.

Prawdziwym problemem z iframe s jest to, że zawartość iframe będzie wymagała wielu żądań sieciowych do pobrania. Plik index.html najwyższego poziomu zostaje odebrany, jego skrypty są ładowane, a jego kod HTML jest analizowany — ale potem przeglądarka musi zainicjować kolejne żądanie kodu HTML elementu iframe , poczekać na jego otrzymanie, przeanalizować i załadować jego skrypty oraz wyrenderować zawartość iframe . W wielu przypadkach kod JavaScript elementu iframe nadal musiałby się rozkręcić, wykonać własne wywołania interfejsu API i pokazywać znaczące dane dopiero po zwróceniu tych wywołań interfejsu API i przetworzeniu danych do przeglądania.

Spowoduje to prawdopodobnie niepożądane opóźnienia i artefakty renderowania, zwłaszcza gdy zaangażowanych jest kilka mikroaplikacji. Jeśli aplikacja iframe implementuje SSR, pomoże to, ale nadal nie uniknie konieczności dodatkowych podróży w obie strony.

Tak więc jednym z kluczowych wyzwań, przed którymi stoimy podczas projektowania wdrożenia naszego portalu, jest sposób radzenia sobie z tym problemem w obie strony. Naszym celem jest, aby pojedyncze żądanie sieciowe spowodowało zamknięcie całej strony ze wszystkimi jej mikroaplikacjami, w tym jakąkolwiek treścią, którą każda z nich jest w stanie wstępnie wypełnić. Rozwiązaniem tego problemu jest serwer Yumcha.

Serwer Yumcha

Kluczowym elementem prezentowanego tutaj rozwiązania mikro-frontendowego jest ustawienie dedykowanego serwera do obsługi kompozycji mikroaplikacji. Ten serwer proxy wysyła żądania do serwerów, na których hostowana jest każda mikroaplikacja. To prawda, że ​​konfiguracja tego serwera i zarządzanie nim będzie wymagać pewnego wysiłku. Niektóre podejścia do mikro-frontendu (np. single-spa) próbują zrezygnować z takich specjalnych konfiguracji serwera w imię łatwości wdrażania i konfiguracji.

Jednak koszt ustanowienia tego odwrotnego proxy jest więcej niż równoważony przez korzyści, które uzyskujemy; w rzeczywistości istnieją ważne zachowania aplikacji opartych na mikrofrontendzie, których po prostu nie możemy osiągnąć bez tego. Istnieje wiele komercyjnych i bezpłatnych alternatyw dla utworzenia takiego zwrotnego serwera proxy.

Odwrotny serwer proxy, oprócz kierowania żądań mikroaplikacji do odpowiedniego serwera, kieruje również żądania makroaplikacji do serwera makroaplikacji. Ten serwer w specjalny sposób przygotowuje kod HTML dla skomponowanej aplikacji. Po otrzymaniu żądania o index.html z przeglądarki za pośrednictwem serwera proxy pod adresem URL, takim jak http://macroapp.example.com , pobiera index.html , a następnie poddaje go prostej, ale kluczowej transformacji przed zwróceniem to.

W szczególności HTML jest analizowany pod kątem <yumcha-portal> , co można łatwo zrobić za pomocą jednego z kompetentnych parserów HTML dostępnych w ekosystemie Node.js. Używając atrybutu src do <yumcha-portal> , nawiązywany jest kontakt z serwerem, na którym działa mikroaplikacja, i pobierany jest jego index.html — w tym treść renderowana po stronie serwera, jeśli taka istnieje. Wynik jest wstawiany do odpowiedzi HTML jako <script> lub <template> , aby nie został wykonany przez przeglądarkę.

Ilustracja architektury serwera Yumcha. Przeglądarka komunikuje się z odwrotnym serwerem proxy, który z kolei komunikuje się z makroaplikacją i każdą z mikroaplikacji. Krok macroapp przekształca i wstępnie wypełnia główny plik index.html aplikacji.

Do zalet tej konfiguracji należy przede wszystkim to, że przy pierwszym żądaniu pliku index.html dla złożonej strony serwer może w całości pobrać poszczególne strony z poszczególnych serwerów mikroaplikacji — łącznie z treścią renderowaną przez SSR, jeśli any — i dostarczaj pojedynczą, kompletną stronę do przeglądarki, w tym zawartość, która może być użyta do wypełnienia ramki iframe bez dodatkowych podróży w obie strony serwera (przy użyciu niewykorzystanego atrybutu srcdoc ). Serwer proxy zapewnia również, że wszelkie szczegóły dotyczące miejsca, z którego obsługiwane są mikroaplikacje, są ukryte przed wzrokiem ciekawskich. Wreszcie upraszcza to kwestie związane z CORS, ponieważ wszystkie żądania aplikacji są wysyłane do tego samego źródła.

Po powrocie do klienta tag <yumcha-portal> zostaje utworzony i znajduje treść w miejscu, w którym został umieszczony w dokumencie odpowiedzi przez serwer, aw odpowiednim czasie renderuje iframe i przypisuje treść do swojego atrybutu srcdoc . Jeśli nie używamy iframe s (patrz poniżej), zawartość odpowiadająca temu <yumcha-portal> jest wstawiana albo do cienia DOM elementu niestandardowego, jeśli tego używamy, albo bezpośrednio w dokumencie.

W tym momencie mamy już częściowo działającą aplikację opartą na mikrofrontendzie.

To tylko wierzchołek góry lodowej, jeśli chodzi o interesującą funkcjonalność serwera Yumcha. Na przykład chcielibyśmy dodać funkcje, aby kontrolować sposób obsługi odpowiedzi HTTP na błędy z serwerów mikroaplikacji lub jak radzić sobie z mikroaplikacjami, które reagują bardzo wolno — nie chcemy czekać w nieskończoność, aby wyświetlić stronę, jeśli jedna mikroaplikacja nie jest odpowiadam! Te i inne tematy zostawiamy w innym poście.

Logika transformacji macroapp index.html Yumcha może być łatwo zaimplementowana w sposób bezserwerowej funkcji lambda lub jako oprogramowanie pośredniczące dla frameworków serwerowych, takich jak Express lub Koa.

Kontrola mikroaplikacji oparta na skrótach

Wracając do strony klienta, jest jeszcze jeden aspekt implementacji mikroaplikacji, który jest ważny dla wydajności, leniwego ładowania i renderowania bez szarpnięć. Moglibyśmy wygenerować tag iframe dla każdej mikroaplikacji, albo z atrybutem src — który powoduje kolejne żądanie sieciowe — albo z atrybutem srcdoc wypełnionym treścią wypełnioną dla nas przez serwer. Ale w obu tych przypadkach kod w tym iframe rozpocznie się natychmiast, włączając w to ładowanie wszystkich jego tagów skryptów i linków, ładowanie początkowe oraz wszelkie początkowe wywołania API i powiązane przetwarzanie danych — nawet jeśli użytkownik nigdy nie uzyskuje dostępu do danej mikroaplikacji.

Naszym rozwiązaniem tego problemu jest początkowo przedstawianie mikroaplikacji na stronie jako małych nieaktywnych skrótów, które można następnie aktywować. Aktywacja może być kierowana albo przez region mikroaplikacji, który pojawia się w widoku, przy użyciu słabo wykorzystywanego interfejsu API IntersectionObserver , albo, częściej, przez wcześniejsze powiadomienia wysyłane z zewnątrz. Oczywiście możemy również określić, że mikroaplikacja ma być aktywowana natychmiast.

W każdym razie, kiedy i tylko wtedy, gdy mikroaplikacja jest aktywna, element iframe jest faktycznie renderowany, a jego kod jest ładowany i wykonywany. Jeśli chodzi o naszą implementację z użyciem LitElement i zakładając, że stan aktywacji jest reprezentowany przez activated zmienną instancji, otrzymalibyśmy coś takiego:

 render() { if (!this.activated) return html`{this.placeholder}`; else return html` <iframe srcdoc="${this.content}" @load="${this.markLoaded}"></iframe>`; }

Komunikacja między mikroaplikacjami

Chociaż mikroaplikacje tworzące makroaplikację są z definicji luźno powiązane, nadal muszą być w stanie komunikować się ze sobą. Na przykład mikroaplikacja nawigacyjna musiałaby wysłać powiadomienie, że inna mikroaplikacja wybrana właśnie przez użytkownika powinna zostać aktywowana, a aplikacja, która ma zostać aktywowana, musi otrzymywać takie powiadomienia.

Zgodnie z naszym minimalistycznym nastawieniem, chcemy uniknąć wprowadzania wielu maszyn przekazujących wiadomości. Zamiast tego, w duchu komponentów webowych, użyjemy zdarzeń DOM. Zapewniamy trywialny interfejs API rozgłaszania, który wstępnie powiadamia wszystkie kody pośredniczące o zbliżającym się zdarzeniu, czeka na aktywację tych, które zażądały aktywacji w celu aktywacji tego typu zdarzenia, a następnie wysyła zdarzenie do dokumentu, na którym może nasłuchiwać dowolna mikroaplikacja to. Biorąc pod uwagę, że wszystkie nasze elementy iframe są tego samego pochodzenia, możemy dotrzeć z elementu iframe do strony i odwrotnie, aby znaleźć elementy, przeciwko którym można uruchomić zdarzenia.

Rozgromienie

W dzisiejszych czasach wszyscy oczekujemy, że pasek adresu URL w SPA będzie reprezentował stan widoku aplikacji, więc możemy wycinać, wklejać, wysyłać, wysyłać tekst i linkować do niego, aby przejść bezpośrednio do strony w aplikacji. Jednak w aplikacji mikrofrontendowej stan aplikacji jest w rzeczywistości kombinacją stanów, po jednym dla każdej mikroaplikacji. Jak mamy to reprezentować i kontrolować?

Rozwiązaniem jest zakodowanie stanu każdej mikroaplikacji w pojedynczy złożony adres URL i użycie małego routera makroaplikacji, który wie, jak połączyć ten złożony adres URL i rozdzielić go. Niestety wymaga to logiki specyficznej dla Yumcha w każdej mikroaplikacji: odbierania komunikatów z routera macroapp i aktualizowania stanu mikroaplikacji i odwrotnie, informowania routera macroapp o zmianach w tym stanie, aby można było zaktualizować złożony adres URL. Na przykład można sobie wyobrazić YumchaLocationStrategy dla Angulara lub element <YumchaRouter> dla Reacta.

Złożony adres URL reprezentujący stan makroaplikacji. Jego ciąg zapytania dekoduje do dwóch oddzielnych (podwójnie zakodowanych) ciągów zapytań, które następnie mają zostać przekazane do mikroaplikacji, których identyfikatory są określone jako ich klucze.

Sprawa bez ramki iframe

Jak wspomniano powyżej, hosting mikroaplikacji w iframe s ma pewne wady. Istnieją dwie alternatywy: umieścić je bezpośrednio w kodzie HTML strony lub umieścić w cieniu DOM. Obie alternatywy odzwierciedlają nieco zalety i wady iframe , ale czasami na różne sposoby.

Na przykład poszczególne zasady CSP dla mikroaplikacji musiałyby zostać w jakiś sposób połączone. Technologie wspomagające, takie jak czytniki ekranu, powinny działać lepiej niż w przypadku elementów iframe , zakładając, że obsługują one shadow DOM (co jeszcze nie wszystkie). Powinno być proste zaaranżowanie rejestracji pracowników usług mikroaplikacji przy użyciu koncepcji Service Worker „zakres”, chociaż aplikacja musiałaby zapewnić, że jej Service Worker jest zarejestrowany pod nazwą aplikacji, a nie "/" . Żaden z problemów z układem związanych z iframe nie dotyczy metod wbudowanego lub cieniowanego modelu DOM.

Jednak aplikacje zbudowane przy użyciu frameworków, takich jak Angular i React, prawdopodobnie nie będą szczęśliwe żyjąc w trybie inline lub w cieniu DOM. W tym przypadku prawdopodobnie będziemy chcieli użyć iframe s.

Metody inline i shadow DOM różnią się pod względem CSS. CSS będzie czysto zamknięty w cieniu DOM. Jeśli z jakiegoś powodu chcielibyśmy udostępnić CSS zewnętrzny za pomocą shadow DOM, musielibyśmy użyć konstruowalnych arkuszy stylów lub czegoś podobnego. Dzięki wbudowanym mikroaplikacjom wszystkie CSS byłyby udostępniane na całej stronie.


Ostatecznie implementacja logiki dla mikroaplikacji inline i shadow DOM w <yumcha-portal> jest prosta. Pobieramy zawartość danej mikroaplikacji z miejsca, w której została wstawiona na stronę przez logikę serwera jako element HTML <template> , klonujemy ją, a następnie dołączamy do elementu, który LitElement nazywa renderRoot , który zwykle jest cieniem DOM elementu, ale może również być ustawiony na sam element ( this ) dla przypadku wbudowanego (nie-shadow DOM).

Ale poczekaj! Treść obsługiwana przez serwer mikroaplikacji to cała strona HTML. Nie możemy wstawić strony HTML dla mikroaplikacji, wraz z tagami html , head i body , w środku strony dla macroapp, prawda?

Rozwiązujemy ten problem, wykorzystując dziwactwo tagu template , w którym opakowana jest zawartość mikroaplikacji pobrana z serwera mikroaplikacji. Okazuje się, że kiedy nowoczesne przeglądarki napotykają znacznik template , chociaż go nie „wykonują”, przetwarzają go, a czyniąc to usuwają nieprawidłową zawartość, taką jak <html> , <head> i <body> , zachowując ich wewnętrzną zawartość. Tak więc tagi <script> i <link> w <head> , jak również zawartość <body> , są zachowane. Właśnie tego chcemy, aby wstawić zawartość mikroaplikacji na naszą stronę.

Architektura mikro-frontendu: diabeł tkwi w szczegółach

Mikrofrontendy zakorzenią się w ekosystemie aplikacji internetowych, jeśli (a) okażą się lepszym podejściem architektonicznym oraz (b) będziemy mogli wymyślić, jak je wdrożyć w sposób, który spełni niezliczone praktyczne wymagania dzisiejszej sieci.

Jeśli chodzi o pierwsze pytanie, nikt nie twierdzi, że mikrofrontendy są odpowiednią architekturą dla wszystkich przypadków użycia. W szczególności nie byłoby powodu, dla którego jeden zespół rozwijałby od podstaw do przyjęcia mikrofrontendów. Pytanie, jakie typy aplikacji w jakich rodzajach kontekstów mogłyby najbardziej skorzystać na wzorcu mikro-frontendu, zostawię innym komentatorom.

Jeśli chodzi o implementację i wykonalność, widzieliśmy, że istnieje wiele szczegółów, którymi należy się zająć, w tym kilka, o których nawet nie wspomniano w tym artykule – w szczególności uwierzytelnianie i bezpieczeństwo, duplikacja kodu i SEO. Niemniej jednak mam nadzieję, że ten artykuł przedstawia podstawowe podejście do implementacji mikrofrontendów, które po dalszym udoskonaleniu mogą sprostać wymaganiom świata rzeczywistego.

Bibliografia

  • Mikro fronty — robienie tego w stylu kątowym — część 1
  • Mikro fronty — robienie tego w stylu kątowym — część 2
  • Ewoluowanie aplikacji AngularJS przy użyciu mikrofrontendów
  • Mikrofronty
  • Mikroserwisy UI — odwracanie antywzorca (mikro frontendy)
  • Mikroserwisy UI — antywzorek?
  • Tworzenie strony przy użyciu Micro-Frontends opiera się na podejściu podobnym do Yumcha, jak reverse proxy i SSI, co bardzo polecam.
  • Zasoby mikrofrontendów
  • Podium
  • Nie rozumiem mikro-frontendów. To całkiem dobry przegląd typów architektur mikro-frontendowych i przypadków użycia.
  • Bezserwerowe mikronakładki wykorzystujące Vue.js, AWS Lambda i Hypernova
  • Micro Frontends: świetny, kompleksowy przegląd.