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 :


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.

TANSTAAFL

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.

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 :


Zagraj to jeszcze raz, Sam

OK, czyli ostatnio skończyliśmy na tym, że trzeba ustawić wartość środowiskową przed startem skryptu. Chciałem do tego zaprząc narzędzie o nazwie rake - czyli menadżer budowania dla ruby.


Idea, która stoi za rake jest ciekawa. Postanowiono w skryptach do budowania ruby pisać w ..... ruby. Proste i genialne jednocześnie. Poza tym fajna gra słów w nazwie. Nazwa pochodzi od wcześniejszego narzędzia make dla C++ - tylko zamieniono literkę "M" na literkę "R" jak Ruby. Poza tym rake w tłumaczeniu to grabie.



Pasuje idealnie, jeżeli przyjąć ostatni trend porównywania tworzenia oprogramowania do projektowania i utrzymywania ogrodu zamiast do budowania domów.

W toku okazało się, że nie muszę ustawiać żadnej zmiennej środowiskowej.

 To zadziała:

coola@sv26 [/home/coola/dsp2017/xp-simulator]# ruby ./test/smoke_test.rb 

Jeżeli tylko ustawie prawidłowo ścieżki require:

require "./src/Client.rb"                                                                                                                                                                                                                    
require "./src/ItPeople.rb"                                                                                                                                                                                                                  
require "./src/Media.rb"     

Te kropki na początku ścieżki są tu kluczowe.

Warto wiedzieć, że null w Ruby nazywa się nil:

def test_if_there_are_actors_of_that_tragedy                                                                                                                                                                                               
                                                                                                                                                                                                                                             
    client = Client.new                                                                                                                                                                                                                      
    it_people = ItPeople.new                                                                                                                                                                                                                 
    media = Media.new                                                                                                                                                                                                                        
                                                                                                                                                                                                                                             
    assert_not_nil(client);                                                                                                                                                                                                                  
    assert_not_nil(it_people);                                                                                                                                                                                                               
    assert_not_nil(media);                                                                                                                                                                                                                   
                                                                                                                                                                                                                                             
  end   

A więc ostateczny skrypt rake o nazwie Rakefile będzie po prostu:

task default: %w[test]                                                                                                                                                                                                                       
                                                                                                                                                                                                                                             
task :test do                                                                                                                                                                                                                                
  ruby "./test/smoke_test.rb"                                                                                                                                                                                                                
end 

Odpalanie projektu poleceniem rake daje takie wyniki:

coola@sv26 [/home/coola/dsp2017/xp-simulator]# rake                                                                                                                                                                                          
/usr/share/ruby-2.3.1/bin/ruby ./test/smoke_test.rb                                                                                                                                                                                          
Loaded suite ./test/smoke_test                                                                                                                                                                                                               
Started                                                                                                                                                                                                                                      
..                                                                                                                                                                                                                                           
                                                                                                                                                                                                                                             
Finished in 0.000537369 seconds.                                                                                                                                                                                                             
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2 tests, 4 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications                                                                                                                                                        
100% passed                                                                                                                                                                                                                                  
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
3721.84 tests/s, 7443.67 assertions/s  

Napisaliśmy to jeszcze raz, Sam. Tym razem działa :)

Cały czas  mam z tyłu głowy potrzebę wersjonowania projektu i wysyłania go na serwer.
Dodaję pliki:

coola@sv26 [/home/coola/dsp2017/xp-simulator]# git add .                                                                                                                                                                                     
coola@sv26 [/home/coola/dsp2017/xp-simulator]# git status                                                                                                                                                                                    
On branch master                                                                                                                                                                                                                             
Your branch is up-to-date with 'origin/master'.                                                                                                                                                                                              
Changes to be committed:                                                                                                                                                                                                                     
  (use "git reset HEAD <file>..." to unstage)                                                                                                                                                                                                
                                                                                                                                                                                                                                             
        new file:   Rakefile                                                                                                                                                                                                                 
        deleted:    smoke_test.rb                                                                                                                                                                                                            
        new file:   src/Client.rb                                                                                                                                                                                                            
        new file:   src/ItPeople.rb                                                                                                                                                                                                          
        new file:   src/Media.rb                                                                                                                                                                                                             
        new file:   test/smoke_test.rb     

Kommituję lokalnie:

coola@sv26 [/home/coola/dsp2017/xp-simulator]# git commit -m "Frist actors of the system and rake building tool addition"                                                                                                                    
[master dcfeb38] Frist actors of the system and rake building tool addition                                                                                                                                                                  
6 files changed, 36 insertions(+), 11 deletions(-)                                                                                                                                                                                          
create mode 100644 Rakefile                                                                                                                                                                                                                 
delete mode 100644 smoke_test.rb                                                                                                                                                                                                            
create mode 100644 src/Client.rb                                                                                                                                                                                                            
create mode 100644 src/ItPeople.rb                                                                                                                                                                                                          
create mode 100644 src/Media.rb                                                                                                                                                                                                             
create mode 100644 test/smoke_test.rb 

Ups, literówka w kommicie w wyrazie "Frist... Miałem na myśli "First....
Jak to naprawić?
Wujek Stack mówi, że tak:

coola@sv26 [/home/coola/dsp2017/xp-simulator]# git commit --amend -m "First actors of the system and rake building tool addition"      

Historia projektu:

coola@sv26 [/home/coola/dsp2017/xp-simulator]# git log 

Rzeczywiście potwierdza zmiany:

commit 1f6a0632411d98ca1340a4ce6d48dffd68b79f88
Author: coola <coola@o2.pl>                                                                                                                                                                                                                  
Date:   Thu Mar 23 16:37:59 2017 +0100                                                                                                                                                                                                       
                                                                                                                                                                                                                                             
    First actors of the system and rake building tool addition                                                                                                                                                                               
                                                                                                                                                                                                                                             
commit 984f97a26cbf9ec32eed6b6304f0ac1ede1770ef                                                                                                                                                                                              
Author: coola <coola@o2.pl>                                                                                                                                                                                                                  
Date:   Fri Mar 10 11:03:08 2017 +0100                                                                                                                                                                                                       
                                                                                                                                                                                                                                             
    First class. Test class                                                                                                                                                                                                                  
                                                                                                                                                                                                                                             
commit aef10c1c8f493e5a3ef0cfe654a12c89c3a0c616                                                                                                                                                                                              
Author: coola <coola@o2.pl>                                                                                                                                                                                                                  
Date:   Tue Mar 7 13:36:07 2017 +0100                                                                                                                                                                                                        
                                                                                                                                                                                                                                             
    Pierwszy commit z plikiem README       

Teraz, jak już mam stabilne środowisko, mogę się pokusić o próbę implementacji komunikacji między aktorami tej tragedii, ale to już w następnym odcinku :)

Ups, zapomniałem wysłać kommitów na serwer:

coola@sv26 [/home/coola/dsp2017/xp-simulator]# git push origin                                                                                                                                                                               
Username for 'https://github.com': coola                                                                                                                                                                                                     
Password for 'https://coola@github.com':                                                                                                                                                                                                     
Counting objects: 9, done.                                                                                                                                                                                                                   
Delta compression using up to 8 threads.                                                                                                                                                                                                     
Compressing objects: 100% (5/5), done.                                                                                                                                                                                                       
Writing objects: 100% (9/9), 904 bytes | 0 bytes/s, done.                                                                                                                                                                                    
Total 9 (delta 0), reused 0 (delta 0)                                                                                                                                                                                                        
To https://github.com/coola/xp-simulator.git                                                                                                                                                                                                 
   984f97a..1f6a063  master -> master        

Uff, zawsze czuję ulgę, gdy zapisuję coś gdzieś indziej niż w jednym miejscu.