Mikroserwisy – API gateway, czyli drzwi wejściowe do naszego systemu

Zapomnijmy na chwilę o mikroserwisach pomimo, że mowa o nich w tytule. Wyobraźmy sobie serwer udostępniający dane o użytkownikach pewnego portalu społecznościowego. Niech będą to szczegółowe dane takie jak imię, nazwisko, ale i ostatnie aktywności na portalu czy też opublikowane posty. Taki serwer udostępnia pewnie jakieś API do pobierania tych informacji zgromadzonych w bazie danych. Powiedzmy, że mamy metodę GetPerson(), która to wykorzystując warstwę aplikacyjną odpytuje repozytoria o dane osoby z innej tabeli wczytujemy ostatnie aktywności a jeszcze z innej ostatnie posty użytkownika. W tej sytuacji aplikacyjna kliencka odpytuje nasze API, odpowiednio prezentuje dane i mamy to.

(źródło: https://docs.microsoft.com/pl-pl/dotnet/architecture/microservices/architect-microservice-container-applications/direct-client-to-microservice-communication-versus-the-api-gateway-pattern)

Problemy rozproszonej lokalizacji zasobów

Wróćmy do mikroserwisów. Sytuacja nieco komplikuje się, bo dane interesujące aplikacje klienckie są rozsiane po różnych zakamarkach systemu. W ostateczności moglibyśmy w końcu dociec gdzie co jest zlokalizowane, w jakim formacie musimy odpytać dany mikroserwis (albo zmusić wszystkie do wystawiania Rest API itd.), ale rodzi to wiele problemów:

  1. Co z uwierzytelnianiem? Czy każdy mikroserwis musi się tym przejmować?
  2. Jak zminimalizować wpływ zmian w strukturze i funkcjonalności mikroserwisów na działanie klientów?
  3. Co ze skalowaniem? Co jeżeli zwiększamy liczbę instancji danego serwisu? Kto decyduje o adresie docelowym zapytania?
  4. Załóżmy, że zmieniamy odpowiedzialności mikroserwisów, wprowadzamy nowe, usuwamy stare. Jakkolwiek zmieniamy wewnętrzną strukturę systemu. Jak wyeliminować zmiany po stronie klientów API?

Rozwiązanie

Jak można się tego spodziewać istnieje rozwiązanie, które upraszcza większość z problemów, które mogą się pojawić w kontekście interakcji między klientem a serwerem w architekturze mikroserwisów. Możemy wprowadzić dodatkowy komponent pośredni między warstwą aplikcji, a aplikacją kliencką, a jest nim API Gateway.

API Gateway to pewien rodzaj reverse proxy. Jego rolą jest odizolowanie reszty mikroserwisów od kwesti związanych z obsługą zapytań z aplikacji klienckich, a więc będzie on przekierowywał zapytania z klienta do odpowiednich serwisów. Odnosząc się do analogii z tytułu posta, API Gateway to drzwi wejściowe do naszego systemu. Z punku widzenia aplikacji klienckiej wszystko za tymi drzwiami jest ukryte i jeżeli nie podamy jej czegoś przez te drzwi to tego nie dostanie. Nie pozwalamy na wejście oknami czy też przez balkon.

Zalety

Co dzięki temu zyskujemy?

  1. Klient nie troszczy się ani o lokalizację mikroserwisów, ani o to w którym mikroserwisie zlokalizowany jest interesujący go zasób, ani o to ile instancji danego mikroserwisu jest dostęne, ani o to czy zmienimy strukturę mikroserwisów, ba, on nie troszczy się o to czy ma doczynienia z mikroserwisami w ogóle!
  2. Umożliwiamy odpytywanie systemu przez wygodne API, które może być dostosowanie API pod konkretnych klientów, bez konieczności implementacji tego w każdym mikroserwisie.
  3. Mamy tylko jeden punkt w którym uwierzytelniamy użytkownika.
  4. Możemy łączyć, filtrować, sortować i w różny sposób manipulować danymi w taki sposób by dostosować je do oczekiwań aplikacji klienckiej.
  5. Pozwalamy mikroserwisom rozmawiać między sobą w formacie, który jest dla nich wygodny, ponieważ nie ma to wpływu na klienta
  6. Możemy wersjonować API wystawione przez nasz Gateway w jeden sposób, a API wewnętrzne wykorzystywane przez mikroserwisy w naszym systemie w całkowicie inny sposób.
  7. Łatwiejsze zastosowanie load balancingu. W specyficznym przypadku API Gateway może być load balancerem.
  8. Łatwiej zastosować blue/green deployment. Dzięki Gateway’owi po deployu możemy przekierować ruch ze starych do nowych serwisów i staje się to bajecznie proste.

Wady

Jakie mamy minusy tego rozwiązania:

  1. Jako, że z natury mamy teraz pojedynczy punkt dostępu do systemu, w przypadku niedostępności tego serwisu, cały system staje się niedostępny. Jeżeli mamy problem z wydajnością w API Gatewayu wpływa on w znacznej mierze na cały system.
  2. Dodajemy następny komponent w systemie
  3. Minimalnie wydłużony czas względem bezpośredniego zapytania klient-mikroserwis

Miałem to szczęście, że dość szybko zostałem zaznajomiony ze wzorcem API Gateway dzięki czemu uniknąłem problemów, które przedstawiłem w początkowej części artykułu.Myślę, że zestawienie wad i zalet pokazuje tutaj jaki potencjał w nim drzemie zwłaszcza przy rozproszonych systemach.

Bibliografia i inne ciekawe linki:

Mikroserwisy – smutne(?) fakty i hejting

Ten cały hype związany z mikroserwisami nie jest przypadkowy. Mikroserwisy , że wydają się być jedyną słuszną drogą, jeżeli nasz projekt ma nie powodować niekontrolowanego wybuchu śmiechu lub też wyrazów politowania na twarzy innych, którzy podążają za trendami i dawno mają już kilkanaście serwisów z których każdy oczywiście korzysta z osobnej bazy danych.

Dlaczego ten entuzjazm w temacie mikroserwisów należy brać z dystansem?

Ceną zalet które uzyskujemy (a opisałem je we wpisie: Mikroserwisy – wprowadzenie i dlaczego chcesz je mieć ) jest spora lista wad:

1. Podróż w nieznane.

Tytuł tego punktu jest celowo przekolorowany aczkolwiek jest w nim sporo prawdy. Musimy się liczyć z mniejszym zrozumieniem tematu pośród developerów. Mimo całego zamieszania wokół mikroserwisów w ostatnich latach, mimo wszystko architektura monolityczna jest dominującym sposobem tworzenia oprogramowania i często czynnikiem limitującym wprowadzenie takiej architektury może być brak odpowiednich kompetencji. Gdy sam zaczynałem swoją pracę nad projektem wykorzystującym mikroserwisy musiałem niemalże z dnia na dzień przestawić się na całkiem inne myślenie. Nie mając wcześniej styczności z tą architekturą chłonąłem wiedzę na jej temat niemalże z wypiekami na twarzy. Decydując się na tą architekturę trzeba być świadomym, że każdy z developerów będzie musiał wyruszyć w tą podróż i tu rodzi się pytanie czy każdy z nich będzie zdolny i wystarczająco zmotywowany by brnąć tą drogą.

2. Testowanie

Testowanie systemu jest utrudnione poprzez fakt, że trudniej nam odtworzyć stan systemu z chwili wystąpienia błędu. Patrząc pod innym kątem łatwiej testować problemy jeżeli są wyizolowane i dotyczą tylko i wyłącznie jednego mikroserwisu. Logowanie pozwala na złagodzenie problemów z testowaniem, aczkolwiek monolit tutaj wygrywa.

3. Kolaboracja między zespołami i inne implikacje wynikające z prawa Conway’a

Prawo Convey’a zgrubsza mówi o tym, że architektura systemu dąży do odzwierciedlenia struktury organizacyjnej ludzi ją tworzących.

Implementacja funkcjonalności wymaga kolaboracji pomiędzy różnymi serwisami może wymagać kooperacji między różnymi zespołami. Podział odpowiedzialności za mikroserwisy pomiędzy zespoły może wywoływać problemy gdy implementacja funkcjonalności przebiega na pograniczu wielu mikroserwisów. Nie jest może to do wielka wada, ale raczej wyzwanie i rzecz na którą warto zwrócić uwagę.

4. Dodatkowa złożoność związana z tworzeniem rozproszonego systemu

Z natury jeżeli mamy do czynienia z monolityczną architekturą, a komunikacja odbywa się wewnątrz procesu, unikamy konieczności martwienia się o to czy inny mikroserwis jest akurat dostępny, czy dane na których operujemy są wystarczająco aktualne (synchronizacja pomiędzy mikroserwisami) i wiele innych.

5.  Mikroserwisy od początku trwania projektu mogą być ryzykowne

Tworzenie architektury zorientowanej na mikroserwisy od zera może przynieść więcej strat niż pożytku. Mając ograniczoną więdzę nt. wymagań odnośnie tworzonego systemu (kto się z tym nie spotkał?) możemy dokonać błędnych decyzji nt. podziału funkcjonalności na mikroserwisy co niesie za sobą dużo większe konsekwencje niż w wypadku monolitu. Dlatego też często zalecaną praktyką jest tworzenie monolitu z luźno powiązanymi modułami, a następnie rozbijanie go na mikroserwisy gdy kształt systemu się wykrystalizuje. Unikamy wtedy przerzucania kodu pomiędzy różnymi mikroserwisami, a nawet pomiędzy różnymi zepołami!

6. Konieczna większa wszechstronność zespołów

Więcej umiejętności DevOpsowych wymaganych od zespołów rozwijających i utrzymujących mikroserwisy. Często wymaganiem jest aby zespół przeprowadził proces rozwijania mikroserwisu od implementacji do deploymentu i utrzymania. W monolitycznej architekturze mniej z tych obowiązków spoczywa na programistach. Wypuszczanie wersji zazwyczaj jest dużo rzadsze i często są obsługiwane przez dedykowanych ludzi.

7. Infrastruktura

Przy mniejszych systemach duży narzut tworzenia i utrzymywania infrastruktury, który niekoniecznie musi się opłacać.

8. Trudna kontrola niezawodności systemu.

W monolicie pula spraw które mogą pójść nie tak jest mniejsza niż przy mikroserwisach. Oczywiście jeżeli monolit leży to leży wszystko. W przypadku mikroserwisów często jesteśmy w dużym stopniu uzależnieni od infrastruktury sieciowej, dostęności zależnych mikroserwisów, szyny wiadomości i innych. To wszystko sprawia, że musimy być w stanie reagować na problemy w każdym z tych obszarów gdyż jeżeli mamy kilka komponentów z których każdy jest krytyczny dla działania systemu to prawdopodobieństwo, że system jest w pełni dostępny w danej chwili jest iloczynem prawdopodobieństwa dostępności każdego z tych komponentów.

Można złagodzić tutaj wymaganie dostępności poprzez komunikację asynchroniczną i architekturę zorientowaną na zdarzenia.

9. Krytyczność posiadania monitoringu i logowania

Jest to poniekąd implikacja kilku poprzednich punktów. Wiele miejsc w systemie wymaga uwagi więc kluczową sprawą jest posiadanie monitoringu i logowania. Nie zliczę ilości razy gdy załamywała mnie tymczasowa niedostępność logowania w systemie w którym musiałem zdebugować jakiś błąd. Nagle złożoność takiego problemu zwiększa się kilkakrotnie. Podobnie brak monitoringu sprawia, że błądzimy po omacku.

Podsumowanie

Każda w wymienionych cech systemów opartych o mikroserwisy powoduje, że warto dwa, a nawet trzy razy zastanowić się nad wprowadzeniem ich do naszego systemu. Mądzej czasami jest wstrzymać się z taką decyzją jak już będziemy ich pewni, nie dlatego, że jest to modne, ale dlatego, że przyniesie to wymierne korzyści w naszym konkretnym projekcie.

Mikroserwisy – wprowadzenie i dlaczego chcesz je mieć

Architektura mikroserwisów

Podział systemu na mikroserwisy jest to styl projektowania w którym dzielimy funkcjonalność na niezależnie deployowalne serwisy. Każdy z tych serwisów odpowiada za część funkcjonalności systemu, są one często usytuowane na różnych maszynach a komunikacja między odbywa się najczęściej przez HTTP/REST API lub też przez wiadomości przesyłane pomiędzy nimi. Nasuwa się pytanie po co? Po to by ułatwić sobie życie, wyeliminować nieprzespane noce związane z wielkimi i ryzykownymi wdrożeniami oraz by móc powiedzieć, że stosujesz mikroserwisy. Z doświadczenia wiem, że każdy z tych argumentów jest niesamowicie ważny i sprawia, że nie będziesz chcieć wracać do „starego” podejścia.

Wśród najczęściej wymienianych firm wykorzystujących z sukcesami mikroserwisy wymienia się takich potentatów jak: Uber, Netflix czy też PayPal.

Netfix

Uber

PayPal

Architektura oparta o mikroserwisy powinna charakteryzować się tym, że każdy z jej elementów jest luźno powiązany (loosely coupled) z innymi. Tak tak, powinna, lecz „sprawny” programista może ukryć zależności przed niewprawionym okiem i dalej tkwić w monolicie. Tutaj też potrzebny jest stały wysiłek by utrzymywać luźne powiązania, to nie silver bullet (niespodzianka!). Dzięki temu zmiany mogą być wprowadzane niezależnie, przez różne zespoły w różnych częściach systemu.

Wymóg tego by każdy mikroserwis mógł być niezależnie deployowalny jak i to, że ma być luźno powiązany z innymi powoduje, że chcemy też unikać ukrytych zależności takich jak współdzielenie baz danych pomiędzy mikroserwisami. Każdy mikroserwis powinien mieć swoją bazę danych. Taki wymóg oczywiście niesie za sobą konsekwencje i musimy zapewnić spójność danych pomiędzy wieloma serwisami.

2017-07-24 20_42_50-BlogGD - microservices pt1.png - Paint

Co zyskujemy?

Jakie są zalety takiej architektury? Na początku nie byłem świadomy ich istnienia, zaraz po ich poznaniu lista zalet to były po prostu obietnice. Teraz, po latach spędzonych na pracy nad mikroserwisami widzę, że nie były to puste obietnice.

1. Każdy serwis może być osobno rozwijany.

Wybór technologii dla jednego serwisu często (ale nie zawsze) nie narzuca technologii dla współpracujących serwisów. Mikroserwisy komunikujące się przez HTTP/REST są tu szczególnie łatwe w izolacji. Przykładowo w projekcie nad którym obecnie pracuję wykorzystujemy mikroserwisy w językach c#, python czy też javascript. Umożliwiamy dzięki temu eksplorację innych technologii poprzez wykorzystanie jej np. w jednym z mikroserwisów. Dzięki tego typu architekturze takie eksperymenty, nawet w wypadku nie do końca pozytywnych rezultatów, nie rzutują negatywnie na cały system.

2. Drastyczne skrócenie interwału pomiędzy releasami.

Dzięki możliwości osobnego wdrażania pojedynczych mikroserwisów, taki release jest zarówno mniej ryzykowny jak i częstszy. Jeden deploy? Dzień jak codzień. 3 deploye? Czemu nie? 5+ deploymentów? Bywa i tak 🙂

3. Zmniejszenie złożoności.

System, poprzez zmniejszenie powiązań między poszczególnymi jego częściami charakteryzuje się mniejszą złożonością co wpływa m. in. na błędogenność i czas jego życia. Architektura monolitu ma tendencje do niekorzystnego spadku produktywności rozwoju na dalszych etapach trwania projektu. Mikroserwisy „zrobione dobrze” są bardziej odporne na próbę czasu przede wszystkim z racji lepszej enkapsulacji logiki, możliwości łatwiejszej izolacji problemów, a także wymuszenia lepszej modularności systemu.

4. Skalowalność.

Umożliwienie skalowania poprzez zwiększanie liczby instancji danego mikroserwisu. Jeżeli zidentyfikujemy mikroserwis, który jest wąskim gardłem w naszym systemie możemy zwielokrotnić liczbę jego instancji poprawiając tym samym wydajność całego systemu. Nie jest to coś co otrzymujemy „za darmo”. Wymaga to odpowiedniej kultury devopsowej. Ba, w moim obecnym projekcie po dłuższym czasie nadal nie wygląda to tak jak powinno. Na szczęście coraz popularniejsze wykorzystanie usług chmurowych upraszcza wprowadzenie skalowalności znacząco! Przykładowo AWS EC2  czy też AWS ECS .

5. Bardziej naturalny podział pracy.

Lepsza alokacja zasobów, łatwiejszy podział prac i zwiększona odpowiedzialność zespołów za serwisy, którymi się opiekują to następne z zalet tego podejścia do projektowania systemu. Architektura monolityczna w której odpowiedzialność jest bardziej rozmyta nie sprzyja efektywnej pracy.

6. Circut breaker.

Jeżeli coś nie działa to jest duża szansa, że reszta systemu jest w stanie działać mimo to. Jak uzyskać tego typu niezawodność? Możemy wykorzystać tu podejście w którym definiujemy pewnego rodzaju komponent, który przy odpytywaniu zależności, którą jest inny mikroserwis, a właśniwie jego API, kontroluje stan zdrowia tego API. Jeżeli stan zdrowia przez kilka kolejnych zapytań nie jest zadowalający, przykładowo brak jest połączenia z serwisem, bądź zwraca on HTTP 500 możemy zdecydować o wyłączeniu funkcjonalności jaką oferuje. Dzięki temu nie odpytujemy na darmo serwisu który być może tylko tymczasowo jest niedysponowany oraz dajemy użytkownikowi szansę skorzystania z innych funkcjonalności systemu.

Oczywiście lista zalet nie wyczerpuje w pełni tematu, gdyż architekturę mikroserwisów można zrealizować na różne sposoby przez co możemy uzyskać nieco inny ich zestaw. Osobiście wydaje mi się, że zmniejszona złożoność systemu to największy plus gdyż jest to istotny czynnik który wpływa na opłacalność projektów, a nadzwyczaj często jest niedoceniany.

Bibliografia i inne ciekawe materiały: