Monorepo – Kiedy warto wybrać? 5 heurystyk do rozważenia.

Parę lat temu wszystko, co było związane z danym projektem, trzymałem w jednym repozytorium – coś na wzór dzisiejszego określenia monorepo. Potem nadszedł czas dzielenia projektów na mniejsze części — zafascynowanie mikroserwisami. Każdy serwis w naturalny sposób, od razu lądował w nowym repozytorium.

Takie rozbijanie powodowało kilka problemów np. wersjonowanie i kompatybilność, duplikacja kodu, zależne od siebie wdrożenia, brak jednego standardu kodowania, pomimo że, utrzymywaliśmy wszystkie repozytoria jako jeden zespół.

Teraz, coraz częściej wracamy do wzorca monorepo, czyli jednego repozytorium, ale powiem szczerze, to nie jest pogoń za trendem. Dla mnie to dodatkowa technika, którą mogę zastosować, gdy będzie dla mnie korzystna.

Czy w projekcie, który startujemy (lub rozwijamy) warto wybrać podejście monorepo? W tym artykule wyjaśnię pięć heurystyk, które warto zastosować do wywołania dyskusji i podejmowania decyzji.

Z poniższą listą będziesz krok dalej, gdy nie masz pojęcia, od czego zacząć.

Tylko uważaj!

Nie stosuj tej listy od razu w swoim zespole. Niech lista będzie inspiracją!

Rozbuduj ją. Przekształć. W Twoim przypadku na decyzję mogą wpływać zupełnie inne czynniki.

Zazwyczaj nie jest łatwo odpowiedzieć sobie na pytanie: Czy monorepo jest dobre dla mojego projektu?

1. Czy mam spójność technologiczną?

Podczas analizy wymagań i projektowania architektury, dość szybko jesteśmy w stanie dostrzec granice pomiędzy poszczególnymi częściami naszego projektu. To może nastąpić z różnych przyczyn m.in.:

  • odmienny kontekst biznesowy,
  • inny model skalowania,
  • specyficzny sposób przetwarzania danych.

Wyodrębniamy wtedy poszczególne części projektu na osobne aplikacje, które mają swoje przeznaczenie. Jeśli jesteśmy na tym etapie, to warto zadać następujące pytanie.

Czy to ciągle będzie ten sam język programowania?

Jeśli odpowiadasz na to pytanie twierdząco, to mamy zgodność technologiczną. To ważna informacja i jeśli takową mamy, to warto trzymać te aplikacje razem.

Idąc krok dalej, warto zadać następne pytanie.

Czy to ciągle będzie ten sam framework?

Jeśli odpowiadasz na to pytanie tak, to mamy idealną zgodność technologiczną.

W takim wypadku będziemy używać prawdopodobnie tych samych narzędzi: aplikacji CLI wspomagających generowanie kodu, package manager’a, narzędzia do statycznej analizy kodu, formatowania kodu itp.

Nie ma potrzeby wprowadzania tych samych konfiguracji czy ich późniejszych zmian na kilka repozytoriów. Trzymajmy je razem.

2. Czy współdzielę kod pomiędzy aplikacjami?

Projekt, którego byłem częścią, zaczął zmagać się ze współdzieleniem interfejsów do API. Chcieliśmy, aby były wspólne i odzwierciedlały aktualny stan API.

Na początku można było przyjąć zasadę submodułów Git. Jednak to ciągle osobne repozytorium, o które należy dbać. Wiele razy zdarzało się, że ktoś zapomniał o wprowadzeniu w nim odpowiedniej poprawki po zmianach w API. To się po prostu nie sprawdzało.

Potem pojawiły się większe fragmenty kodu, które spokojnie mogły być zewnętrznymi bibliotekami. To wciąż ten sam problem. Dodajemy nową funkcję do biblioteki, potem wszędzie musimy ją uaktualnić do nowszej wersji.

Kilka nowych Merge Requestów, odpowiednia kolejność scalania. Eh… Uciążliwa sprawa.

Prywatne biblioteki, publikowane i zaciągane na zasadzie zewnętrznych zależności generowały więcej ogólnej obsługi niż wartość z dodanej funkcji.

Gdy widzę duplikację kodu pomiędzy aplikacjami, to zwykle chce wyodrębnić ją do niezależnej biblioteki. Pomysł jest dobry, jednak ma swoje wady, których na początku możemy nie być świadomi. Pojawia się: cykl wydań, wersjonowanie, kompatybilność wsteczna, podbijanie wersji w aplikacjach, które korzystają z biblioteki. Całkiem spora kupka rzeczy do ogarnięcia.

Monorepo daje nam szybszą obsługę takiego współdzielonego kodu. Szczególnie gdy, to tylko my korzystamy z tego kodu, a faktyczna duplikacja nie powinna być biblioteką zarządzaną poza naszym projektem.

To po prostu reużywalny kod w naszym systemie. Nigdzie indziej.

3. Czy mam podobny model wdrożenia (CI/CD)?

Każda z naszych aplikacji w pewnym momencie jest wdrażana na środowisko testowe lub produkcyjne.

Być może proces wdrożenia, jest identyczny i składa się np. z poniższych kroków (przykład bardzo uogólniony) dla wszystkich naszych aplikacji:

  1. Zbuduj Docker Image z aplikacją;
  2. Opublikuj obraz w Docker Registry;
  3. Zaktualizuj serwis do nowej wersji w Amazon ECS.

Gdy proces wdrożenia, zgadza się pomiędzy aplikacjami, to mamy dobry punkt kontrolny. Skrypty automatyzujące będziemy mogli w tym przypadku ponownie użyć bez potrzeby ich kopiowania pomiędzy repozytoriami.

W przeszłości zdarzało mi się tak, że istniał jeden Jenkins Job, który był w stanie wdrażać każdą z aplikacji dużego systemu. Przygotowano wspólny proces dla wszystkich aplikacji. Dzięki temu, gdy pojawiała się zmiana w tym procesie (np. dodanie powiadomienia na kanał Slack), to była ona od razu wykorzystywana we wszystkich wdrożeniach.

4. Czy muszę utrzymywać kompatybilność pomiędzy aplikacjami?

Utrzymywanie kompatybilności pomiędzy komunikującymi się aplikacjami (za pomocą API, kolejki) nie jest prostym wyzwaniem. Gdy pojawia się nowa struktura danych lub istniejąca ulega zmianie, należy się odpowiednio przygotować – np. wspierając nową i starą strukturę w sposób przejściowy.

Sprawa może się jeszcze bardziej skomplikować, jeśli struktura nie zależy od nas i musi się wpasować w cykl wydań.

Wprowadzanie wszystkich niezbędnych zmian pomiędzy projektami w tym samym czasie staje się łatwiejsze. Przykładowo, dodanie wymaganego pola, wszystko możemy dostarczyć za jednym razem. Mając w tym momencie pewność, że aktualny stan kodu, pozwala nam korzystać ze zmiany we wszystkich aplikacjach.

5. Czy istnieje tylko jeden zespół rozwijający aplikacje?

Pracowałem w zespołach, które utrzymywały polyrepo dla projektu. Każdy mikroserwis, posiadał własne repozytorium.

Utrzymywanie uwagi przy ciągłym wprowadzaniu zmian przez zespół było męczące. Mam siedem repozytoriów, które muszę obserwować. Sprawdzać, czy coś się nie zmieniło, aktualizować każde z nich, czasem instalować nowe paczki i przebudowywać aplikację.

Już nie wspomnę o wprowadzaniu nowych osób. Do dziś pamiętam te grymasy na twarzy, gdy trzeba postawić siedem różnych aplikacji, aby całościowo projekt mógł działać. Czasem nie było takiej potrzeby, ale jako zespół dbaliśmy o wszystkie aplikacje, więc finalnie każdy mógł programować wszędzie…

W monorepo łatwiej jest zorganizować automatyzację takiego procesu. Szybko można rozstawić projekt, uruchomić go czy zaktualizować po wprowadzonych zmianach. Tym bardziej, gdy skorzystamy z narzędzi wspomagających pracę z monorepo np. rush.

Monorepo – czy to dobry pomysł?

Pamiętaj, że każdy projekt, zespół czy wymagania biznesowe wpływają na podejmowanie decyzji. Jeżeli po kilku miesiącach pracy zaczynamy odczuwać dyskomfort z pracy z monorepo / polyrepo, to warto zrobić sobie retrospekcję i zastanowić się nad usprawnieniami.

Przedstawione problemy zawsze można rozwiązać inaczej. Może się okazać, że wdrożenie CI/CD wygląda nieco inaczej, ale mimo wszystko warto aplikację umieścić z pozostałymi w jednym monorepo.

Czasem wystarczy stworzyć odpowiednie skrypty, stworzyć dodatkowy pipeline w CI/CD. A czasem będziemy zmuszeni do zmiany naszego podejścia.

To, co zaproponowałem, to nie są twarde ramy. To punkty do dyskusji. Ich głównym celem jest wyzwolenie rozmowy w zespole.

A na co Ty zwracasz jeszcze uwagę? Masz jakieś punkty kontrolne?

PS. O wyborze Monorepo vs Polyrepo dyskutowaliśmy także na naszym podcaście.