Jak zdusić monolit i przejść na mikroserwisy? - Część 1

22.02.2022 | Rafał Danowski

Wstęp

Każdy z nas spotkał się kiedyś z monolitem. Duża baza kodu realizującego wiele procesów biznesowych lub technicznych, która z czasem może urosnąć do rozmiarów nieogarnianych przez zwykłych śmiertelników, sprawiając, że wdrożenie nowej, „małej” zmiany może stać się długim i żmudnym procesem. Nie oznacza to, że monolity z reguły są złe – mają swoje plusy, które czynią je dobrym wyborem w rozmaitych sytuacjach, na przykład na początku projektu, gdy jeszcze nie wiemy, jak będzie wyglądać.

Co w przypadku gdy wady monolitu będą się bardzo dawać we znaki? Każda nowa zmiana to parę miesięcy pracy i planowania. Wdrożenie nowej osoby to obciążający czasowo proces. A co jeśli doświadczony pracownik pójdzie w tym czasie na L4? Można się spodziewać, że praca stanie. Możliwe, że dla części z Was takie obrazy są znajome. W takiej sytuacji ewolucja zbytnio nie pomoże. Trzeba wykonać prawdziwą rewolucję i przemianować monolit na mikroserwisy.

 

No dobra, ale dlaczego mikroserwisy mają pomóc? 

Nie twierdzę, że mikroserwisy są super w każdym calu i należy je stosować, gdzie tylko można. Mają swoje zalety – pomagają rozwiązać problemy monolitów. Jednakże sama ich idea wprowadza zupełnie nowe zagadnienia do rozwiązania, nierzadko równie złożone jak bolączki monolitu. Przyjrzyjmy się wadom mikroserwisów zanim przejdziemy do tego, w jaki sposób mogą nam one pomóc z problemami monolitu.

 

Wady mikroserwisów 

Wad mikroserwisów istnieje naprawdę sporo. Skupię się na tych najważniejszych, aby podkreślić, że implementacja mikroserwisów to nie bułka z masłem.

 

Dużo nakładu pracy

Nie ma się co oszukiwać – wdrożenie mikroserwisów wymaga od developerów dużo większego nakładu pracy niż przy zastosowania monolitu. Musimy rozwiązać problemy nieistniejące w pojedynczej aplikacji, takie jak service discovery, stworzyć automatyczny proces CI/CD per mikroserwis (biada tym, którzy chcą ręcznie instalować serwisy), mieć systemy agregacji logów, monitoringu, zarządzania sekretami. Musimy też więcej uwagi poświęcić na odpowiednią obsługę błędów i mechanizmy obronne na wypadek problemów m.in. z infrastrukturą. Dodatkowo, często należy w taki sposób przeprojektować aplikację, aby stała się bezstanowa. To wszystko należy wykonać, by móc mówić o wdrożeniu pełnej architektury mikroserwisowej. Na każdy element należy poświęcić odpowiednią ilość czasu i pracy, żeby było to zrobione dobrze.

 

Wyzwania związane z bezpieczeństwem

Zaczynając od zarządzania, np. certyfikatami w poszczególnych aplikacjach, poprzez implementację OAuth (lub innego protokołu), aż do odpowiedniego zabezpieczenia sieci czy konfiguracji reguł sieciowych – nie należy zapominać o bezpieczeństwie i trzeba dobrać tak wyważone rozwiązania, aby ryzyka oraz wydajność były na akceptowalnym przez wszystkich poziomie.

 

Wyzwania związane z bezpieczeństwem Problemy ze zrozumieniem idei mikroserwisów 

Mimo, że o mikroserwisach mówi się już od dłuższego czasu, pełne zrozumienie ich koncepcji może być problematyczne i zdarza się, że prowadzi to do m.in. niepełnych wdrożeń, gdy na przykład mamy paręnaście serwisów w ramach jednej usługi, ale wszystkie korzystają ze wspólnej bazy danych,  co zamazuje korzyści płynące z takiej architektury. Przy implementacji takiego rozwiązania należy upewnić się, że wiedza w ramach organizacji stoi na wystarczającym poziomie, by móc w pełni wykorzystać wszystkie przewagi, jakie dają mikroserwisy.

 

Większy koszt

Serwery aplikacyjne, bazy danych, licencje, czas programistów i DevOpsów, szkolenia – to wszystko kosztuje, czasami kilkukrotnie więcej niż przy rozwiązaniu monolitowym. Warto ten aspekt mieć na uwadze, gdy zdecydujemy się na zbytnie rozdrobnienie serwisów. Może to spowodować ogromne zapotrzebowanie na zasoby serwerowe.

 

Opóźnienia

Istniejące w mikroserwisach połączenie SSL pomiędzy komponentami, ruch sieciowy, dodatkowe elementy w komunikacji, takie jak BFF (Backend For Frontend) lub API Gateway, w przeciwieństwie do rozwiązań w architekturze monolitycznej, powodują wydłużenie czasu wywołania pojedynczego endpointa. Jeśli jednak wszystko zrobisz dobrze, wzrost czasów wywołań nie będzie aż tak wielki i uprzykrzający dla użytkownika. Może skoczyć z ~50ms do ~300ms (wartości podglądowe), co będzie niewielką różnicą dla klientów i pewnie nawet nie zauważą zmiany.

 

Jakie są problemy monolitu i jak mikroserwisy magicznie je rozwiązują?

Żeby nie było zbyt pesymistycznie, po przeczytaniu samych wad mikroserwisów, teraz kolej na przedstawienie jakie problemy mają duże monolity i w jaki sposób zmiana architektury może je rozwiązać.

 

Długi czas wdrożenia nawet małych zmian

Jeśli przy estymacji bardzo prostego zadania, typu „zmień ifa”, zastanawiasz się nad 5 czy 8 punktami, to wiedz, że coś się dzieje. Jednakże to nie szatan opętał Twój projekt, a postępujący dług technologiczny, silne powiązania między modułami lub oba naraz. Tutaj wjeżdżają mikroserwisy całe na biało. Jeśli uda Ci się podzielić monolit na mniejsze serwisy, zerwiesz ze splątanym kodem, przez co zmiana w jednym miejscu nie powinna powodować efektów ubocznych. Łatwiej jest ponadto zarządzać długiem technicznym w przypadku mniejszej aplikacji.

 

„A co to tu robi?” - czyli kilka słów o braku zrozumienia produktu

Napisałeś pewną funkcjonalność. Przetestowałeś – działa. Leci na produkcję, nikt nie zgłasza błędów, pora na CS-a. Mija pół roku. Dostajesz zadanie, by nieco urozmaicić tę funkcjonalność. Przekonany, że wszystko pamiętasz, wchodzisz do IDE na pewniaka, jednak coś Ci nie gra – nie pamiętasz, by było tutaj tyle tego kodu. Jakieś dziwne konstrukcje, wywołania innych serwisów, komentarze typu „// nie ruszać, działa, ale nie wiem dlaczego”. Załamujesz się i składasz wypowiedzenie, trzeba było wyjechać do Chile i wypasać alpaki. To wszystko może brzmi nieprawdopodobnie, jednakże podobne sytuacje są możliwe, gdy nad całym projektem pracuje wielu programistów bez jasnych podziałów na elementy aplikacji. W architekturze mikroserwisowej jeden zespół jest odpowiedzialny za jeden serwis. W ten sposób niemożliwe jest dokonanie zmian w swoim obszarze odpowiedzialności przez kogoś innego bez wiedzy danego zespołu. Nie musisz też uczyć się wszystkich kruczków i hacków w pozostałych modułach – możesz skupić się na swoim klocku. Co więcej, jak przyjdzie nowa osoba do Twojego zespołu, łatwiej będzie ją wdrożyć, gdyż będzie miała dużo mniejszy zakres do nauki na start. Dodatkowym plusem jest zwiększenie bus factoru – nawet jeśli odejdą 1–2 osoby, które były związane z projektem od początku, będzie relatywnie łatwo je zastąpić ze względu na łatwiejsze wdrożenie nowych ludzi lub poszerzenie wiedzy i odpowiedzialności już zatrudnionych pracowników.

 

„Parralel” vs „parallel” – jak mała literówka może powodować wielkie problemy 

Niech rzuci kamieniem ten, kto nigdy nie spushował literówki. Czasami może się zdarzyć, że zostanie ona niewychwycona w CR. Może też przedostać się większy błąd z powodu braku wiedzy ludzi wykonujących Code Review lub po prostu niechlujnego sprawdzenia kodu, a nawet braku korekty. Jeśli taka pomyłka jest niewielka, to jeszcze nie ma tragedii, ale jeśli przez nią aplikacja przestaje działać lub w ogóle nie chce się uruchomić, to jest to ogromny problem w przypadku projektu monolitowego. Każda taka przerwa w działaniu usługi może powodować ogromne straty finansowe i wizerunkowe firmy, zwłaszcza jeśli mowa o instytucji bankowej lub innej o podobnej kluczowej działalności. W przypadku wystąpieniu błędu należy przejść cały proces od nowa, tj. wykonać fixa, przeprowadzić CR, zmergować z obowiązującą gałęzią (oczywiście main, nie wolno już mieć mastera), puścić testy, ewentualnie przetestować manualnie, uruchomić pipeline i zdeployować na środowisku produkcyjnym. To wszystko trwa, klienci się niecierpliwią, menadżerowie są zdenerwowani – wszechobecny stres. W takim środowisku łatwo o kolejny błąd i wszystko należy zaczynać od nowa… Przy użyciu mikroserwisów, zwłaszcza korzystających z automatycznych procesów CI/CD, problem jest dużo mniejszy lub w ogóle nie występuje. Po pierwsze, baza kodu jest mniejsza, przez co zmiany są niewielkie i trudniej przeoczyć jakiś błąd. Po drugie, wiedza na temat modułu w zespole powinna być wysoka, więc osoba sprawdzająca wydajniej przeprowadzi CR. Kluczowym aspektem jest to, że nawet jeśli jakiś bug się przedostanie przez sito, to trafi tylko do pewnej części usługi – większość procesów będzie dalej działać, klienci tego nie odczują i straty będą dużo mniejsze.

 

Podjąłem decyzję – chcę mikroserwisy 

Przekonało Cię to, co wyżej napisałem? Miło mi, ale tak naprawdę każda sytuacja jest inna i należy podjąć odpowiednią decyzję po uprzedniej głębokiej analizie. Zanim zrobisz coś pochopnie, sprawdź jakie masz możliwości czasowe, finansowe, merytoryczne. Zastanów się czy naprawdę warto – może w Twoim przypadku refactor obecnego rozwiązania będzie korzystniejszy, może wystarczy podzielić monolit na moduły (modularny monolit). Zmiana architektury to długi, monotonny proces i lepiej, żeby decyzja była poparta solidnymi argumentami, może nawet jakimś POC-em.

Jeśli jednak po analizach dalej jesteś przekonany, że czas skończyć z monolitem i zacząć korzystać z dobrodziejstw mikroserwisów, zapraszam do dalszej lektury, gdzie przedstawię jak może wyglądać taki proces.

 

Proces zmiany architektury - najważniejsza zasada

Wyobraź sobie dwa scenariusze – w pierwszym podchodzisz do tematu stopniowo, poświęcając na migrację tylko część czasu sprintu, w drugim natomiast 100% czasu pracy to dostosowanie obecnej aplikacji do mikroserwisów. Przyjrzyjmy się teraz, jak im idzie z czasem (disclaimer – czasy i wartości są podglądowe):

  • Na starcie wszystko jasne – oba projekty są monolitami (nazwijmy je projekt A i B).
  • Po pierwszych 3 miesiącach sprawdźmy jak im idzie. W projekcie A nie najgorzej – na produkcji udało się wydzielić 1 mikroserwis i do tego wdrożono 3 nowe funkcjonalności i poprawiono 7 błędów. W projekcie B też wydaje się, że jest nieźle – wprawdzie na prodzie nic się nie zmieniło, za to już wydzielono 3 serwisy, na razie działa tylko Arkowi na jego komputerze, ale jak u niego działa, to wszędzie też będzie.
  • Po pół roku możemy dostrzec już pewną różnicę. Zespół A wygrzał już swoją konfigurację poprzez POC-a na produkcji pojedynczego serwisu i ma ogarniętą większość infrastruktury. Wydzielił 8 mikroserwisów, które pomyślnie działają dla klientów i monolit skurczył się do ~30% swojego pierwotnego rozmiaru. Dodatkowo cały czas zespół rozwija swój produkt i klienci oraz inwestorzy są zadowoleni – może nowości i poprawki nie trafiają tak szybko jak wcześniej, ale ostatecznie znajdują się na produkcji. W zespole B humory są umiarkowane – nie było żadnych zmian na produkcji od 6 miesięcy. Klienci coraz gorzej oceniają aplikację, mnożą się błędy oraz nowe funkcje do dodania. Udało się wydzielić wszystkie mikroserwisy, jednakże są uruchomione tylko na devie. Rozpoczynają się prace nad próbą implementacji rozwiązania na PRD.
  • Po kolejnych paru miesiącach zaglądamy do naszych zespołów A i B. Projekt A jest już w pełni w architekturze mikroserwisowej. Udało się załatwić przejście na tyle gładko, dzięki zaimplementowanym rozwiązaniom, że klienci nawet nie odczuli, jak wielka zmiana się dokonała w ich ulubionej aplikacji. Dodatkowo cały czas były naprawiane błędy i dostarczana nowa wartość w postaci nowych lub usprawnionych funkcjonalności. Zespół B zmienił się nie do poznania (być może dlatego, że większość programistów złożyła wypowiedzenia). Ostatecznie udało się wykonać podmianę architektury, za trzecim razem – 2 pierwsze próby skończyły się fiaskiem, trzeba było rollbackować całe wdrożenia. Na backlogu kłębią się bugfixy oraz nowe funkcje, które powinny były trafić parę miesięcy temu na proda.

 

Nie wiem jak Ty, ale ja wolałbym stosować podejście zespołu A. Tylko jak tego dokonać? O tym wszystkim przeczytasz w drugiej części artykułu.