Przewodnik po Monorepos dla kodu front-end

Opublikowany: 2022-03-11

Monorepos to gorący temat do dyskusji. Ostatnio pojawiło się wiele artykułów o tym, dlaczego powinieneś i nie powinieneś używać tego typu architektury w swoim projekcie, ale większość z nich jest w ten czy inny sposób stronnicza. Ta seria jest próbą zebrania i wyjaśnienia jak największej ilości informacji, aby zrozumieć, jak i kiedy korzystać z monorepo.

Monorepozytorium to koncepcja architektoniczna, która zasadniczo zawiera całe znaczenie w swoim tytule. Zamiast zarządzać wieloma repozytoriami, trzymasz wszystkie izolowane części kodu w jednym repozytorium. Pamiętaj o słowie odizolowany — oznacza to, że monorepo nie ma nic wspólnego z monolitycznymi aplikacjami. W jednym repozytorium możesz przechowywać wiele rodzajów aplikacji logicznych; na przykład strona internetowa i jej aplikacja na iOS.

Porównanie monorepo, pojedynczego repo i multirepo

Ta koncepcja jest stosunkowo stara i pojawiła się około dekadę temu. Google był jedną z pierwszych firm, które przyjęły to podejście do zarządzania swoimi bazami kodu. Możesz zapytać, jeśli istnieje od dekady, to dlaczego jest tak gorącym tematem dopiero teraz? Przeważnie w ciągu ostatnich 5-6 lat wiele rzeczy uległo dramatycznym zmianom. ES6, preprocesory SCSS, menedżery zadań, npm itp. — w dzisiejszych czasach, aby utrzymać małą aplikację opartą na React, musisz radzić sobie z pakietami projektów, zestawami testowymi, skryptami CI/CD, konfiguracjami Dockera i kto wie co jeszcze. A teraz wyobraź sobie, że zamiast małej aplikacji musisz utrzymywać ogromną platformę składającą się z wielu obszarów funkcjonalnych. Jeśli myślisz o architekturze, będziesz chciał zrobić dwie główne rzeczy: oddzielić obawy i uniknąć duplikatów kodu.

Aby tak się stało, prawdopodobnie będziesz chciał wyizolować duże funkcje do niektórych pakietów, a następnie używać ich za pośrednictwem jednego punktu wejścia w swojej głównej aplikacji. Ale jak zarządzasz tymi pakietami? Każdy pakiet będzie musiał mieć własną konfigurację środowiska przepływu pracy, a to oznacza, że ​​za każdym razem, gdy chcesz utworzyć nowy pakiet, będziesz musiał skonfigurować nowe środowisko, skopiować wszystkie pliki konfiguracyjne i tak dalej. Lub, na przykład, jeśli musisz coś zmienić w swoim systemie kompilacji, będziesz musiał przejrzeć każde repozytorium, wykonać zatwierdzenie, utworzyć żądanie ściągnięcia i czekać na każdą kompilację, co bardzo spowalnia. Na tym etapie spotykamy się z monorepo.

Zamiast posiadania wielu repozytoriów z własnymi konfiguracjami, będziemy mieli tylko jedno źródło prawdy — monorepo: jeden program uruchamiający zestaw testów, jeden plik konfiguracyjny Dockera i jedną konfigurację dla pakietu Webpack. I nadal masz skalowalność, możliwość oddzielenia problemów, współdzielenie kodu ze wspólnymi pakietami i wiele innych zalet. Brzmi ładnie, prawda? No cóż, tak jest. Ale są też pewne wady. Przyjrzyjmy się dokładnym zaletom i wadom używania monorepo na wolności.

Zalety Monorepo:

  • Jedno miejsce do przechowywania wszystkich konfiguracji i testów. Ponieważ wszystko znajduje się w jednym repozytorium, możesz raz skonfigurować CI/CD i bundlera, a następnie ponownie użyć konfiguracji, aby zbudować wszystkie pakiety przed opublikowaniem ich na odległość. To samo dotyczy testów jednostkowych, e2e i integracyjnych — Twój CI będzie mógł uruchomić wszystkie testy bez konieczności zajmowania się dodatkową konfiguracją.
  • Łatwa refaktoryzacja funkcji globalnych dzięki atomowym zatwierdzeniom. Zamiast wykonywać żądanie ściągnięcia dla każdego repo, zastanawiając się, w jakiej kolejności kompilować zmiany, wystarczy wykonać atomowe żądanie ściągnięcia, które będzie zawierało wszystkie zatwierdzenia związane z funkcją, nad którą pracujesz.
  • Uproszczone publikowanie pakietów. Jeśli planujesz zaimplementować nową funkcję wewnątrz pakietu, która jest zależna od innego pakietu ze współdzielonym kodem, możesz to zrobić za pomocą jednego polecenia. Jest to funkcja, która wymaga dodatkowych konfiguracji, które zostaną później omówione w części tego artykułu poświęconej przeglądowi narzędzi. Obecnie istnieje bogaty wybór narzędzi, w tym Lerna, Yarn Workspaces i Bazel.
  • Łatwiejsze zarządzanie zależnościami. Tylko jeden pakiet.json . Nie musisz ponownie instalować zależności w każdym repozytorium, gdy chcesz zaktualizować swoje zależności.
  • Ponowne użycie kodu ze współdzielonymi pakietami, przy jednoczesnym zachowaniu ich izolacji. Monorepo pozwala na ponowne wykorzystanie pakietów z innych pakietów, jednocześnie utrzymując je w izolacji od siebie. Możesz użyć odwołania do pakietu zdalnego i korzystać z niego za pośrednictwem pojedynczego punktu wejścia. Aby użyć wersji lokalnej, możesz użyć lokalnych dowiązań symbolicznych. Ta funkcja może zostać zaimplementowana za pomocą skryptów bash lub poprzez wprowadzenie dodatkowych narzędzi, takich jak Lerna lub Yarn.

Wady Monorepo:

  • Nie ma możliwości ograniczenia dostępu tylko do niektórych części aplikacji. Niestety, nie możesz udostępnić tylko części swojego monorepo — będziesz musiał przyznać dostęp do całej bazy kodu, co może prowadzić do problemów z bezpieczeństwem.
  • Niska wydajność Git podczas pracy nad projektami na dużą skalę. Ten problem zaczyna pojawiać się tylko w ogromnych aplikacjach z ponad milionem commitów i setkami deweloperów wykonujących swoją pracę jednocześnie każdego dnia w tym samym repozytorium. Staje się to szczególnie kłopotliwe, ponieważ Git używa ukierunkowanego wykresu acyklicznego (DAG) do reprezentowania historii projektu. Przy dużej liczbie zatwierdzeń każde polecenie, które porusza się po wykresie, może stać się wolniejsze w miarę pogłębiania się historii. Wydajność również spada ze względu na liczbę ref (tj. gałęzi lub tagów, które można rozwiązać poprzez usunięcie ref, których już nie potrzebujesz) i ilość śledzonych plików (a także ich wagę, nawet jeśli problem z ciężkimi plikami można rozwiązać za pomocą Git LFS).

    Uwaga: obecnie Facebook próbuje rozwiązać problemy ze skalowalnością VCS, łatając Mercurial i prawdopodobnie wkrótce nie będzie to tak duży problem.

  • Dłuższy czas budowy. Ponieważ będziesz mieć dużo kodu źródłowego w jednym miejscu, Twoje CI zajmie znacznie więcej czasu, aby uruchomić wszystko w celu zatwierdzenia każdego PR.

Przegląd narzędzi

Zestaw narzędzi do zarządzania monorepo stale się powiększa i obecnie naprawdę łatwo jest się zgubić w całej różnorodności systemów budowania dla monorepo. Korzystając z tego repozytorium, zawsze możesz być świadomy popularnych rozwiązań. Ale na razie rzućmy okiem na narzędzia, które są obecnie intensywnie używane w JavaScript:

  • Bazel to system kompilacji zorientowany na monorepo firmy Google. Więcej o Bazel: super-bazel
  • Yarn to narzędzie do zarządzania zależnościami JavaScript, które obsługuje pojedyncze repozytorium za pośrednictwem obszarów roboczych.
  • Lerna to narzędzie do zarządzania projektami JavaScript z wieloma pakietami, zbudowane na Yarn.

Większość narzędzi stosuje bardzo podobne podejście, ale są pewne niuanse.

Ilustracja procesu CI/CD repozytorium monorepo git

Zagłębimy się w przepływ pracy Lerny, a także inne narzędzia w części 2 tego artykułu, ponieważ jest to dość obszerny temat. Na razie przyjrzyjmy się, co jest w środku:

Lerna

To narzędzie naprawdę pomaga w radzeniu sobie z wersjami semantycznymi, konfigurowaniu przepływu pracy przy tworzeniu, wypychaniu pakietów itp. Główną ideą Lerny jest to, że Twój projekt ma folder pakietów, który zawiera wszystkie izolowane części kodu. Poza pakietami masz główną aplikację, która na przykład może znajdować się w folderze src. Prawie wszystkie operacje w Lernie działają według prostej zasady — iterujesz wszystkie swoje pakiety i wykonujesz na nich pewne działania, np. zwiększasz wersję pakietu, aktualizujesz zależność wszystkich pakietów, budujesz wszystkie pakiety itp.

Z Lerną masz dwie możliwości wykorzystania swoich pakietów:

  1. Bez wypychania ich do zdalnego (NPM)
  2. Wysyłanie pakietów do zdalnego

Używając pierwszego podejścia, możesz używać lokalnych referencji dla swoich pakietów i zasadniczo nie przejmujesz się dowiązaniami symbolicznymi, aby je rozwiązać.

Ale jeśli używasz drugiego podejścia, jesteś zmuszony zaimportować swoje pakiety ze zdalnego. (np. import { something } from @yourcompanyname/packagename; ), co oznacza, że ​​zawsze otrzymasz zdalną wersję swojego pakietu. W przypadku programowania lokalnego, będziesz musiał utworzyć dowiązania symboliczne w katalogu głównym twojego folderu, aby program pakujący rozwiązywał lokalne pakiety zamiast używać tych, które znajdują się w twoim node_modules/ . Dlatego przed uruchomieniem Webpacka lub Twojego ulubionego bundlera będziesz musiał uruchomić lerna bootstrap , który automatycznie połączy wszystkie pakiety.

Ilustracja przedstawiająca przestrzeń nazw modułów w pakiecie z jednym węzłem

Przędza

Yarn początkowo jest menedżerem zależności dla pakietów NPM, który początkowo nie był zbudowany do obsługi monorepozytorium. Jednak w wersji 1.0 programiści Yarn udostępnili funkcję o nazwie Workspaces . W momencie wydania nie był tak stabilny, ale po pewnym czasie stał się użyteczny w projektach produkcyjnych.

Obszar roboczy to w zasadzie pakiet, który ma swój własny pakiet . W rzeczywistości możesz w jakiś sposób zarządzać bez Yarn Workspaces za pomocą bash i mieć dokładnie taką samą konfigurację, ale to narzędzie pomaga uprościć proces instalacji i aktualizacji zależności na pakiet.

Na pierwszy rzut oka Yarn ze swoimi obszarami roboczymi zapewnia następujące przydatne funkcje:

  1. Pojedynczy folder node_modules w katalogu głównym dla wszystkich pakietów. Na przykład, jeśli masz packages/package_a i packages/package_b — z własnym package.json — wszystkie zależności zostaną zainstalowane tylko w katalogu głównym. To jedna z różnic między sposobem działania Yarn i Lerny.
  2. Łączenie symboliczne zależności w celu umożliwienia lokalnego tworzenia pakietów.
  3. Pojedynczy plik blokady dla wszystkich zależności.
  4. Skoncentrowana aktualizacja zależności w przypadku, gdy chcesz ponownie zainstalować zależności tylko dla jednego pakietu. Można to zrobić za pomocą flagi -focus .
  5. Integracja z Lerną. Możesz łatwo sprawić, by Yarn zajął się całą instalacją/symlinkami, a Lerna zajęła się publikacją i kontrolą wersji. Jest to jak dotąd najpopularniejsza konfiguracja, ponieważ wymaga mniej wysiłku i jest łatwa w obsłudze.

Przydatne linki:

  • Obszary robocze przędzy
  • Jak zbudować projekt mono-repo TypeScript

Bazel

Bazel to narzędzie do budowania aplikacji na dużą skalę, które może obsługiwać zależności wielojęzyczne i obsługiwać wiele nowoczesnych języków (Java, JS, Go, C++ itp.). W większości przypadków używanie Bazela do małych i średnich aplikacji JS jest przesadą, ale na dużą skalę może przynieść wiele korzyści ze względu na swoją wydajność.

Ze swojej natury Bazel wygląda podobnie do Make, Gradle, Maven i innych narzędzi, które pozwalają na kompilacje projektów w oparciu o plik, który zawiera opis reguł kompilacji i zależności projektu. Ten sam plik w Bazel nazywa się BUILD i znajduje się w obszarze roboczym projektu Bazel. Plik BUILD używa swojego Starlark, czytelnego dla człowieka języka kompilacji wysokiego poziomu, który wygląda bardzo podobnie do Pythona.

Zwykle nie będziesz miał do czynienia z BUILDEM , ponieważ istnieje wiele szablonów, które można łatwo znaleźć w sieci i które są już skonfigurowane i gotowe do rozwoju. Ilekroć chcesz zbudować swój projekt, Bazel zasadniczo wykonuje następujące czynności:

  1. Ładuje pliki BUILD odpowiednie dla celu.
  2. Analizuje dane wejściowe i ich zależności, stosuje określone reguły kompilacji i tworzy wykres akcji.
  3. Wykonuje akcje kompilacji na danych wejściowych do momentu utworzenia końcowych danych wyjściowych kompilacji.

Przydatne linki:

  • JavaScript i Bazel – Dokumentacja do tworzenia projektu Bazel dla JS od podstaw.
  • Reguły JavaScript i TypeScript dla Bazel – Boilerplate dla JS.

Wniosek

Monorepos to tylko narzędzie. Istnieje wiele argumentów na temat tego, czy ma przyszłość, czy nie, ale prawda jest taka, że ​​w niektórych przypadkach narzędzie to wykonuje swoje zadanie i radzi sobie z nim w sposób efektywny. W ciągu ostatnich kilku lat narzędzie to ewoluowało, zyskało znacznie większą elastyczność, pokonało wiele problemów i usunęło warstwę złożoności pod względem konfiguracji.

Nadal jest wiele problemów do rozwiązania, takich jak słaba wydajność Git, ale miejmy nadzieję, że zostanie to rozwiązane w najbliższej przyszłości.

Jeśli chcesz nauczyć się budować solidny potok CI/CD dla swojej aplikacji, polecam Jak zbudować skuteczny początkowy potok wdrożeniowy za pomocą GitLab CI .

Powiązane: Wyjaśnienie ulepszonego przepływu Git