Czysta Architektura Mikroserwisów

Jak skalujesz system komputerowy?
Jedna rzecz powinna być oczywista: w którymś momencie potrzebujesz mieć więcej niż jeden komputer. Był taki czas, i to było całkiem niedawno, kiedy skalowanie mogło być osiągnięte przez czekanie. Po prostu czekałeś, aż komputery staną się szybsze i potężniejsze. Po każdych kilku miesiącach miałeś automatycznie wzrost na skali.

Poniższy tekst jest luźnym tłumaczeniem wpisu bloga Roberta Cecila "Wujka Boba" Martina ze strony :


Proszę o komentarze, jeżeli ta luźność jest zbyt daleko posunięta.

Pomijając, czy była to dobra strategia czy nie; to już tak nie działa. Wraz z nadejściem nowego tysiąclecia, projektanci sprzętu przestali próbować podnosić częstotliwość taktowania zegara i zamiast tego zaczęli rozmnażać rdzenie. Faktem jest, że aby osiągnąć to rozmnożenie - projektanci sprzętu usuwali pamięć podręczną i potoki, których zwykli używać do zwiększania prędkości maszyn jednordzeniowych.
Więc dzisiaj, skalowanie systemów komputerowych oznacza dodawanie więcej rdzeni, i dodawanie więcej serwerów. Nie ma drogi na około. No więc, jak to robisz? Jak rozdzielasz swoją aplikację, aby mogła być uruchamiania na wielu rdzeniach i wielu serwerach?
Jak skalujesz?
Twoja karta graficzna używa jednego podejścia. Posiada wiele procesorów, które działają w strategii lockstep; robią te same operacje w innych obszarach pamięci wewnętrznej. Ta forma obliczeń równoległych na masową skalę jest idealna dla kart graficznych odkąd szybką wydajność grafiki można osiągnąć przez te same przekształcenia na dużych macierzach tych samych danych. W rzeczywistości, superkomputery używały tego podejścia przez dziesięciolecia żeby przewidywać pogodę lub symulować wybuchy jądrowe.
Inną techniką jest tradycyjne podejście trzywarstwowe. Dzielisz swój system na GUI, warstwę pośrednią i bazę danych. Przyporządkowujesz serwery do GUI, kilka do warstwy pośredniej, i jeszcze kilka do bazy danych. Tworzysz zestaw wiadomości (zwykle zawierający zserializowane obiekty), które mogą być przesyłane pomiędzy warstwami. I voilà! Skalowanie.
Mikroserwisy
Ostatnio widzimy jeszcze inną strategię skalowania. Mikroserwisy. Pisałem o nich tu i tu. Jest sławny artykuł od Martina Fowlera i Jamesa Lewisa tu.
Mikroserwis jest małym programem wykonywanym gdzieś na serwerze. Odpowiada na asynchroniczne wiadomości. Zwykle te wiadomości są dostarczane przez HTTP w formacie REST; chociaż to szczegół, a nie wymaganie.
System posiada architekturę mikroserwisów wtedy kiedy jest ułożony z wielu współpracujących mikroserwisów; zwykle bez centralnej kontroli.
Czysta Architektura i Mikroserwisy
Teraz rozważ tak zwaną Czystą Architekturę. Zauważ, że używa ona wiele elementów, włączając Przypadki Użycia, Prezenterów, Bramki. Te komponenty otrzymują żądania w postaci podstawowych struktur danych (POJO), które przychodzą ze źródła, oddzielonego od komponentu przez polimorficzną granicę wejściową.
Zauważ, że komponenty odpowiadają na te żądania poprzez tworzenie podstawowych struktur danych i wysyłanie ich na wyjście, które jest oddzielone od komponentu przez polimorficzną granicę wyjściową.
Czy ten układ może być użyty do stworzenia architektury mikroserwisów?
Oczywiście.
Nic w Czystej Architekturze nie żąda, aby wiadomości były synchroniczne czy asynchroniczne. Nic nie stoi na przeszkodzie, by te żądania i odpowiedzi były przesyłane do innego serwera. Nic w architekturze nie przeszkadza, żeby komponenty były małymi programami komunikującymi się przez HTTP przy użyciu REST.
Więc, architektura mikroserwisów może łatwo dopasować się do Czystej Architektury. W rzeczywistości, gdybym tylko budował system używając mikroserwisów, z całkowitą pewnością podążałbym tą ścieżką.
Miara skalowalności komponentów
Mikroserwis jest tylko sposobem do wdrażania komponentów oprogramowania. Są inne;
i mają różne skalowalności. Oto lista kilku innych opcji wdrażania, w kolejności poziomu skalowalności.

  1. Mikroserwisy wdrożone na wiele serwerów.
  2. Mniejsza liczba serwerów, każdy odpala więcej niż jeden mikroserwis.
  3. Tylko jeden serwer z grupą mikroserwisów uruchamianych jako proste programy.
  4. Usługi odpalane jako wątki w pojedynczej maszynie wirtualnej komunikujące się poprzez kolejki komunikatów.
  5. Dynamicznie linkowane komponenty (JARki albo DLLki) przesyłające wiadomości ze strukturą danych poprzez wywołania funkcji.
Znowu, to powinno być oczywiste, że Czysta Architektura działa tak samo w każdym przypadku z tej listy. Powód tego jest taki, że Czysta Architektura nie przykłada wagi do tego jak komponenty są wdrażane. W rzeczywistości system z dobrą Czystą Architekturą nie wie jakiego sposobu wdrażania używa.
Jeszcze raz powtarzam. Kod w środku komponentów Czystej Architektury nie ma pojęcia, że:
  • to jest działający mikroserwis na niezależnym serwerze komunikującym się z innymi niezależnymi serwerami przez internet,
  • albo to jest mały program pośród wielu odpalonych na pojedynczej maszynie komunikujący się na prostych socketach,
  • albo to jest lekki wątek komunikujący się z innymi lekkimi wątkami na tej samej maszynie wirtualnej używając skrzynek pocztowych czy kolejek,
  • albo to prosty JAR czy DLL komunikujący się z innymi komponentami przy użyciu polimorficznych wywołań funkcji.
I to powinno naprowadzić Cię o czym tak naprawdę jest ten artykuł.
Model Wdrożenia jest Szczegółem.
Jeżeli kod komponentów może być tak napisany, aby mechanizmy komunikacyjne i mechanizmy rozdzielenia procesów były nieistotne, wtedy te mechanizmy są szczegółami. A szczegóły nigdy nie są częścią architektury.

To oznacza, że nie ma czegoś takiego jak architektura mikroserwisów. Mikroserwisy są opcją wdrożenia, a nie architekturą. Dobra architektura wszystkie opcjonalne kwestie utrzymuje otwartymi tak długo, jak to tylko możliwe. Dobra architektura odkłada decyzję, w jaki sposób system będzie wdrożony, aż do ostatniego możliwego momentu.
Zamierzona ignorancja.
Wiele osób z pewnością będzie narzekać na przedstawiony punkt widzenia, że jeżeli nie zaprojektujesz swojego systemu dla mikroserwisów na początku, nie będziesz mógł ich podmienić na końcu.
Tere - fere. Przecież to BDUF
Dobrzy architekci systemów tworzą struktury, których komponenty systemu - czy to przypadki użycia, komponenty widoku, bazy danych, czy czego tam chcesz - nie mają pojęcia w jaki sposób są wdrażane i w jaki sposób komunikują się z innymi komponentami systemu. Zamierzona ignorancja pozwala architektom wybrać właściwy model wdrożeń, który działa w aktualnej sytuacji i pozwala dopasowywać ten model do zmian. Jeżeli system musi się szeroko skalować wdrażasz to na mikroserwisach. Jeżeli system potrzebuje dwóch, trzech serwerów, wdrażasz to na mieszance procesów, wątków i JARów/DLLów. Jeżeli nigdy nie potrzebowałeś więcej niż jednego serwera, możesz wdrożyć to na jednym JAR/DLL.
Łamanie tej zamierzonej ignorancji jest dobrą drogą do przekombinowania projektu systemu. Za często widziałem systemy, które przyjęły trzywarstwowe architektury licząc na skalowalność, tylko po to żeby odkryć, że te systemy nigdy nie będą musiały być wdrażane na więcej niż na jednej maszynie. Jak dużo prostsze mogłoby być to oprogramowanie, gdyby tylko projektanci na początku spróbowali opcji z jednym serwerem i zachowali niezależność komponentów od modelu wdrażania.
Inne sprawy
Oczywiście są inne sprawy do rozważenia. Po pierwsze, jeżeli wdrażasz na mikroserwisach, masz wolną rękę co do wyboru Twojego ulubionego języka. Możesz pisać swoje mikroserwisy w Ruby, Clojure, Javie, C#, C++, C, Assemblerze, Pascalu, Delphi, PHP, Javascriptcie lub nawet w COBOLu. Po drugie, możesz wybrać dowolny framework jaki tylko Ci się podoba. Jeden mikroserwis może używać Ralisów, inny może używać springa, jeszcze inny może używać BOOSTa. Podobnie, każdy mikroserwis może używać innej bazy danych. Jeden może użyć Couch-a, podczas gdy inny może używać SQLServer i jeszcze inny może używać MySQL czy Datomic. Na koniec, mikroserwisy zapewniają duży poziom izolacji. Granice mikroserwisów są ostateczną formą rozdzielenia.
Ten ostatni punkt wymaga uwypuklenia. Jeżeli dwa komponenty komunikują się ze sobą przez HTTP używając REST są bardzo mocno rozdzielone. Jedyną rzeczą łączącą te dwa komponenty razem jest schemat wiadomości REST; innymi słowy interfejs. Nie tylko są rozłączone przez interfejs, są rozłączone także w czasie wdrożenia. Te dwa serwisy nie muszą być odpalone w tym samym czasie; tak samo nie muszą być wyłączane w tym samym czasie. Jest oczywiście możliwość przeładowania mikroserwisu bez przeładowywania tych, które od niego zależą. To oznacza ogromne rozdzielenie.
Ograniczenia idąc w dół skali.
Jak idziesz w dół skali, od mikroserwisów do procesów, do wątków, do JARków zaczynasz tracić niektóre z tych możliwości. Im bliżej jesteś JARków, tym mniej dowolności masz w wyborze języka. Masz także mniej dowolności co do frameworków i baz danych. Jest także większe ryzyko, że interfejsy pomiędzy komponentami będą z czasem coraz bardziej powiązane. I oczywiście ciężko przeładować komponenty, które żyją w jednym skompilowanym programie.
Ale czy na pewno? Właściwie OSGi istnieje w świecie Javy już od jakiegoś czasu. OSGi pozwala Ci przeładować pliki JAR w locie. Nie jest to tak wygodne jak kopnąć mikroserwis, ale nie jest też daleko od tego.
Jeżeli chodzi o języki, prawdą jest, że w ramach pojedynczej maszyny wirtualnej będziesz ograniczony. Z drugiej strony, JVM pozwoli Ci pisać w Javie, Clojure, Scali i JRuby, a to tylko niektóre wymienione.
Więc, tak, jeżeli pójdziesz w dół skali ograniczenia wzrosną; ale być może nie tak bardzo.
Jeżeli chodzi o frameworki i bazy danych, czy to naprawdę coś złego, zwłaszcza we wczesnej fazie rozwoju aplikacji, aby ograniczyć ich liczbę? Czy chcemy naprawdę zacząć z jednym zespołem używającym JPA, a drugim używającym Hibernate? Czy naprawdę chcemy, aby jeden komponent używał bazy Datomic, a drugi używał Oracla? I czy jeżeli pozwolimy na to, to czy nie tworzymy mnóstwa złożoności konfiguracyjnej?
I, na koniec, łączenie przy użyciu interfejsów jest sprawą dyscypliny i dobrego projektowania. Nade wszystko, zwykły obiekt Java (POJO) przesłany przez polimorficzny interfejs nie jest bardziej powiązany niż REST. Trochę troski podczas projektowania komponentów sprawi, że będziemy mieli JARki, których interfejsy są tak luźno rozłączone jak mikroserwis.
Kiedy poruszasz się w górę skali, te ograniczenia odpadają, ale nowe problemy zaczynają się pojawiać. W jakim porządku startujesz system? W jakim porządku go wyłączasz? Jak radzisz sobie z konfiguracją i kontrolą tych wszystkich usług? Co z powielonym kodem? Co z wersjonowaniem formatów wiadomości? Ale zamiast mnie wyliczającego kwestie tutaj, możesz przeczytać o niektórych z nich tu i tu. Wystarczy w tym miejscu powiedzieć, że decyzja, aby użyć mikroserwisów jest decyzją typu "coś za coś" i nie jest przysłowiowym darmowym lunchem.
Monolity i Marketerzy.
Na koniec słówko o nazewnictwie. Niektórzy obrońcy mikroserwisów lubią szufladkować inne podejście jako monolityczne. To ujmujące pojęcie wybrane po to, aby zasugerować: “To jest złe”. Słowo monolit oznacza “jedną skałę”. W domyśle oznacza to, że jeżeli nie używasz mikroserwisów, to musisz mieć wielkiego, zbitego potwora.
To są byzydury marketingowe.
Dobrze zaprojektowany system podążający za Czystą Architekturą jest tak daleko od monolitu jak tylko się da. Wszak, jest to zestaw niezależnych dynamicznie wdrażanych komponentów (JARek albo DLLek), które są luźno połączone ze sobą, mogą być utrzymywane przez różne zespoły, mogą być pisane w ogromie różnych języków i mogą być podmieniane w locie używając narzędzi podobnych do OSGi. Trudno to nazwać monolitycznym.
Podsumowanie i Rada [1]
Z tego wszystkiego mógłbyś sobie pomyśleć, że uważam mikroserwisy za zły pomysł; i że nie powinieneś ich używać. To nie tak. Mikroserwisy są doskonałym modelem wdrożeniowym, jeżeli tylko postarasz się, żeby pasował do Twoich rozwiązań. Jeżeli nie możesz wdrażać na mikroserwisy, to oznacza, że powiązałeś swoją architekturę z konkretnym modelem wdrożenia.
Z tego samego powodu, jeżeli możesz wdrożyć Twój system jedynie na mikroserwisy, wtedy masz powiązaną architekturę z tym konkretnym modelem wdrożenia; i to jest tak samo złe.
To, do czego chcę Cię przekonać, to abyś pomijał każdy konkretny model wdrożenia. Traktuj model wdrożenia jako szczegół, i zostaw sobie otwarty wybór. Zbuduj swój system tak, abyś mógł go wdrażać albo na JARki, albo na mikroserwisy, albo na cokolwiek pomiędzy.
Rozpocznij wdrażanie Twojego systemu na dynamicznie linkowane komponenty (JARki lub DLLki), i stopniowo idź w górę skali jak będzie potrzeba. Nie skacz na górę skali przewidując masową skalowalność. Zostaw sobie tę opcję otwartą zgodnie z Czystą Architekturą.
[1] Kimże jestem żeby dawać takie rady? Przecież jak napisałem w poprzednim artykule, spotkałem się z pojęciem “Mikroserwisy” kilka tygodni temu.
Niedawno odkryłem pojęcie; ale w ciągu ostatnich 40 lat mojej kariery miałem wielokrotnie możliwość projektowania i budowania systemów, które wdrażały komponenty jako niezależne programy dogadujące się ze sobą przez komunikaty. Mikroserwisy mogą być nowym pojęciem; ale ciężko powiedzieć, że to nowy pomysł.

Powyższy tekst jest luźnym tłumaczeniem wpisu bloga Roberta Cecila "Wujka Boba" Martina ze strony :


Proszę o komentarze, jeżeli ta luźność jest zbyt daleko posunięta.

2 komentarze:

  1. "Wiele osób z pewnością będzie narzekać na przedstawiony punkt widzenia, że jeżeli nie zaprojektujesz swojego systemu dla mikroserwisów na początku, nie będziesz mógł ich podmienić na końcu. "

    Chyba nigdy nie widziałem, żeby ktoś polecał zaczynać projekt od mikroserwisów. Wspomniany Martin Fowler również mówił zawsze, żeby zaczynać od monolitu : https://martinfowler.com/bliki/MonolithFirst.html

    OdpowiedzUsuń
  2. Cześć Anonimowy.
    Dzięki za komentarz i link do Martina Fawlera.

    OdpowiedzUsuń