Architektura ITPolecane tematy
Mikroserwisy, czym są i jak z nich korzystać w praktyce
W swoich założeniach mikroserwisy to system prosty – podobny do architektury zorientowanej na usługi – jednak posiadający grupę cech istotnie wyróżniających go od podejścia „klasycznego”. Niektóre osoby, słysząc o mikroserwisach mówią, że stosują je od dawna, spełniając jednak założenia „ubrane w nowe szaty”, które są wynikiem własnych przemyśleń, a nie teorii. Czy to jakkolwiek deprecjonuje podejście mikroserwisowe? Moim zdaniem nie. Pokazuje raczej, że zagadnienie to nie jest tworem sztucznie wypromowanym, lecz ma charakter ewolucyjny.
Dzięki swojej specyfice mikroserwisy mogą rozwiązać kilka istotnych bolączek, z którymi przychodzi mierzyć się obecnie funkcjonującym rozwiązaniom. Warto mieć na uwadze, że oprócz zalet takiej architektury – pojawiają się, także, nowe wyzwania. Jak wiadomo, cykl życia oprogramowania to nie tylko moment uruchomienia pierwszej wersji. Niektóre problemy mogą się ujawnić dopiero po paru latach funkcjonowania środowiska, a jeszcze później zostaną zidentyfikowane, jako powszechnie znany problem. Mikroserwisy nie stanowią, uniwersalnego lekarstwa na wszystkie bolączki IT, jednakże projektując nowe rozwiązanie – bądź podczas rozważań na temat strategii technologicznej organizacji – warto je wziąć pod uwagę.
Obietnica niezależności i odejście od monolitycznych rozwiązań
W przypadku środowisk IT, wraz z ich rozwojem rośnie poziom ich skomplikowania, zależności, ścisłych powiązań komponentów. Dla rozwiązań monolitycznych taki zbiór zależności wydaje się być naturalną implikacją podejścia. W dużym stopniu, dla przypadku rozproszenia architektury, zorientowanej na serwisy SOA (Service Oriented Architecture) – przy przemyślanym projekcie – problem zostaje znacznie zredukowany. Często implementacja takiej architektury jest oparta na „wszechwładnej” szynie, wyposażonej w zbiór ciężkich serwisów.
Mimo wielu zalet podejścia scentralizowanego, rozwój takich rozwiązań, po pewnym czasie, zaczyna przypominać empiryczne sprawdzanie teorii tzw. efektu motyla. Dotknięcie jednego komunikatu, może powodować kolosalne skutki w całym środowisku, w najmniej oczekiwanym miejscu i momencie. Pogoń za reużywaniem ciężkich interfejsów, zaszłości historyczne poszczególnych systemów, braki w dokumentacji – to tylko niektóre z przyczyn tego, że po jakimś czasie rozwój środowiska staje się coraz bardziej koszmarny.
Architektura oparta o mikroserwisy stanowi ciekawą koncepcję, która sprawdza się w przypadku tak małych aplikacji, jak i dużych środowisk. Dzięki swym założeniom może pomóc w sprawniejszym wytwarzaniu oprogramowania, jak i jego utrzymywaniu, przyczyniając się tym samym do efektywniejszego wykorzystania nakładów na IT.
Pomijając aspekt braku komfortu psychicznego członków zespołu analitycznego – funkcjonalnie saperskiego – prowadzenie projektów rozwojowych staje się coraz bardziej problematyczne. Konieczność uwzględniania licznych zależności, istotnie utrudnia stosowanie metodyk zwinnych (agile). Może nie uniemożliwia, lecz z pewnością w sposób istotny redukuje także pojemność kolejnych iteracji, wydłużając tym samym, czas skutecznego wprowadzania zmian.
Natomiast mikroserwisy precyzyjniej definiują sposób realizacji usług. Powinny być małe – najlepiej skupione na realizacji jednej rzeczy – oraz, co ważniejsze, niezależne od siebie. Nie tylko na poziomie interfejsu, ale także kodu, modelu danych, oraz warstwy jego utrwalenia. Te proste założenia mają ogromny wpływ na całe środowisko IT, gdyż dotyczą nie tylko sposobu projektowania, ale także podejścia do utrzymania – dzięki precyzyjniejszej skalowalności, izolacji awarii, itp. Teoretycznie każdy mikroserwis powinien być wdrażalny, niezależnie od pozostałych. Mało tego, powinny one być niezależne od siebie technologicznie, tzn. zmiana platformy powinna być możliwa nawet na poziomie pojedynczego mikroserwisu.
Częściej, niż w podejściu „klasycznym” powinniśmy zadawać sobie pytanie, czy dla danego serwisu – o ile występuje – wybrana warstwa persystencji jest najbardziej optymalna? Może dla danego przypadku warto przechowywać dane inaczej? Teoretycznie każdy mikroserwis może być napisany w innym języku programowania. To, co istotne, możliwość, nie oznacza obligatoryjności. Nie jest tak, że każdy mikroserwis – przykładowo wykorzystujący relacyjną bazę danych – musi posiadać własną, fizyczną instancję. Dotyczy to całego stosu technologicznego, a zwłaszcza języka programowania, użytych bibliotek, kontenerów, itd. Unifikacja technologii ma wiele istotnych zalet przekładających się wprost na minimalizację wymaganych kompetencji w zespołach projektowych.
Projektując mikroserwisy należy często zadawać sobie pytanie o możliwość zmian. Separacja w dużym stopniu dotyczy poziomu konceptu. Im mniejszy będzie wpływ ewentualnej zmiany technologii, lub przebudowy usługi, tym prędzej można ją nazwać mikroserwisem.
Niezależność mikroserwisów łatwo jest zaburzyć przez tworzenie dedykowanej warstwy stanowiącej przedsionek dla dowolnego mikroserwisowego środowiska. Przy większych projektach może to prowadzić do wydzielenia grupy osób odpowiedzialnych za samo „master-api” – co w praktyce jest drogą do utraty wspomnianej wcześniej niezależności. Czy więc jesteśmy skazani na bezpośrednie wystawienie mikroserwisów, bez możliwości agregacyjnej ich kontroli? Co zrobić, gdy chcemy mieć kontrolę np. nad specyficznym routowaniem komunikatów? Sensownym wydaje się tworzenie możliwe uniwersalnej, wieloinstancyjnej warstwy, udostępniającej możliwość jej niezależnego dostosowania konfiguracyjnego przez twórców odpowiedzialnych za poszczególne mikroserwisy.
Mimo wielu zalet podejścia scentralizowanego, rozwój takich rozwiązań, po pewnym czasie, zaczyna przypominać empiryczne sprawdzanie teorii tzw. efektu motyla. Dotknięcie jednego komunikatu, może powodować kolosalne skutki w całym środowisku, w najmniej oczekiwanym miejscu i momencie.
Projektowanie mikroserwisów i ich granulacyjny
Granularność serwisów, to jedno z pierwszych zagadnień, z jakimi przyjedzie nam się zmierzyć. Łatwo powiedzieć, że serwis ma być mały. Nie wyczerpuje to jednak jego specyfikacji, gdyż nie jest tak, że im jest on mniejszy tym lepiej. Powinien być prosty, o ścisłym zakresie odpowiedzialności. Modelowanie najlepiej rozpocząć od świata „biznesowego”. Każdy zidentyfikowany – istotny z punktu widzenia rozwiązania – określony zasób to rzeczownik. To statyczna, opisowa część kandydata na mikroserwis. Przykładowo modelując bibliotekę z książkami do wypożyczenia może to być właśnie… książka. Nie zawsze da się wykonać wprost tak oczywiste mapowanie, w końcu życie projektanta nie może być nudne! Gdy mamy rzeczowniki, pora na czasowniki. Ich zbiór zazwyczaj ograniczymy do znanego CRUD – Create, Read, Update, Delete. W naszym przypadku oznacza to tyle, że książkę będzie można dodać do zbioru, odczytać jej atrybuty, zaktualizować i ją usunąć ze zbiorów.
Dlaczego akurat te cztery czasowniki? Przekłada się to wprost na warstwę implementacji. Najczęściej jest to REST (REpresentational State Transfer). Dlaczego REST nadaje się do architektury mikroserwisowej idealnie? Jest w nim wskazanie na nazwę zasobu (URI). Jest też zbiór rzeczowników (POST, GET, PUT, DELETE), a także znormalizowane określenie statusu wywołania (http response). Prosto i konkretnie. Zgodna z założeniami implementacja daje nam kilka benefitów. Jeśli zostanie zachowane przeznaczenie metody GET – dzięki temu, że z definicji nie zmienia ona stanu zasobu – będziemy mogli wykorzystać (zazwyczaj) gotowe do użycia mechanizmy cache’owania. Bardzo istotną cechą architektury RESTowe jest: HATEOAS (Hypermedia as the Engine of Application State).
Przede wszystkim pozwala on zapewnić kolejną separację – klienta od serwera. Osiągalne to jest, dzięki umożliwieniu definiowania – w odpowiedzi serwera – dostępnych kroków związanych z zasobem. Implementacyjnie koncepcję tę można przyrównać do HTML’owych linków. To pozornie nieistotne podejście istotnie upraszcza przyszły rozwój integracji. Jeśli nad rozwojem czuwa jedna jednostka organizacyjna – benefit nie jest taki oczywisty. Wystarczy jednak spotkać się z sytuacją, gdy klient serwisu jest dostarczany przez inną jednostkę, innego dostawcę, lub po prostu z jednego serwisu korzysta bardzo wiele różnych systemów – wtedy zysk z ograniczenia zmian tylko do strony udostępniającej serwis jest oczywisty. Nie zawsze to da się osiągnąć. Czasem przeszkodą będzie natura zmiany, nie wszystko się przewidzi w opracowanym schemacie, czasem blokadą może być niefortunna implementacja klienta serwisu…
Mikroserwisy precyzyjniej definiują sposób realizacji usług. Powinny być małe – najlepiej skupione na realizacji jednej rzeczy – oraz, co ważniejsze, niezależne od siebie. Nie tylko na poziomie interfejsu, ale także kodu, modelu danych, oraz warstwy jego utrwalenia. Teoretycznie każdy mikroserwis powinien być wdrażalny, niezależnie od pozostałych.
Pomijając liczne zalety podejścia RESTowego, niektóre z pryncypiów stanowić mogą dla projektanta swego rodzaju wyzwanie. Bezstanowość serwisu wpływa bowiem istotnie na bezpieczeństwo. W purystycznym podejściu nie ma mowy o czymś takim, jak sesja o stanie, przechowywanym po stronie serwera. Wynika to z założenia, iż mikroserwis działa na zasadzie funkcji z jawnymi argumentami wywołania. Zatem, jeśli chcemy ograniczyć dostępność zasobu konieczne staje się opracowanie podejścia do realizacji tematu inaczej, niż w oparciu o klasycznie rozumiane sesje. Można próbować wypracować własne rozwiązanie oparte o tokeny, bądź – co w przypadku aspektu bezpieczeństwa jest zalecane – szukać gotowego rozwiązania do reużycia. Tyle co istotne – warto ten aspekt mieć uwzględniony na wczesnym etapie projektowania.
Spójność danych w ramach mikroserwisów
Rozbicie monolitycznych dużych usług na mniejsze wiąże się z tym, że granicą transakcji mogą nie być granice pojedynczego wywołania usługi, lecz ich ciąg. Wtedy ciężar zachowania spójności danych może częściowo leżeć po stronie klienta mikroserwisów. Niezależnie od tego, czy jako warstwę przechowywania danych wykorzystana będzie relacyjna baza danych, czy np. NoSQL – problem zachowania więzów integralności, wycofania niedokończonych transakcji pozostaje aktualny.
Projektując pozornie proste rozwiązanie – łatwo przez to trafić na całkiem sporo dotychczas nierozpatrywanych problemów – zwłaszcza, jeśli Projektant nie miał do czynienia z transakcjami rozproszonymi. Przy takich dylematach warto spróbować zastanowić się, czy odzwierciedlana rzeczywistość jest tak restrykcyjna, jak próbujemy to odwzorować w naszym systemie i z czym się wiąże brak spójności danych. Paradoksalnie może się okazać, że system z mniejszą liczbą ograniczeń jest bardziej przystosowany do codziennego użytku, niż naszpikowany licznymi ograniczeniami, ze sztucznie stworzonymi transakcjami – o źródle wynikającym z technicznych przyzwyczajeń deweloperskich, a nie – modelowanego świata.
Niezależność mikroserwisów łatwo jest zaburzyć przez tworzenie dedykowanej warstwy stanowiącej przedsionek dla dowolnego mikroserwisowego środowiska. Przy większych projektach może to prowadzić do wydzielenia grupy osób odpowiedzialnych za samo „master-api” – co w praktyce jest drogą do utraty wspomnianej wcześniej niezależności.
Architektura oparta o mikroserwisy stanowi ciekawą koncepcję, która sprawdza się w przypadku tak małych aplikacji, jak i dużych środowisk. Dzięki swym założeniom może pomóc w sprawniejszym wytwarzaniu oprogramowania, jak i jego utrzymywaniu, przyczyniając się tym samym do efektywniejszego wykorzystania nakładów na IT. Bardziej komfortową sytuację, pod względem możliwych stopni swobody, mają osoby zaangażowane w budowanie środowiska od podstaw. Jednakże przebudowa środowisk „tradycyjnych” na architekturę, opartą na mikroserwisach także jest możliwa.
Czy warto, taką transformację przeprowadzać, oraz w jaki sposób? To interesujące zagadnienie, na osobny artykuł.
Krzysztof Wilniewczyc jest Architektem Systemów IT z 6-letnim doświadczeniem.