Krzysztof Olszewski
Dyrektor Technologii i Architektury Oprogramowania
Krzysztof Olszewski
Dyrektor Technologii i Architektury Oprogramowania
Z niewielkim ryzykiem pomyłki można powiedzieć, że przyszłością systemów informatycznych jest modułowość. To ryzyko jest tym mniejsze, im bardziej uświadomimy sobie, że modułowość jest obecna w IT od samego początku, ergo nie jest niczym nowym.
Biblią, dekalogiem w inżynierii IT są reguły projektowe z których wyprowadzamy wzorce projektowe i te ostatnie stanowią użyteczny zbiór wzorców postępowania „na co dzień”. Jednak to reguły są pierwotne i najważniejsze. Znajomość i głębokie zrozumienie reguł jest najtrudniejszym etapem w kształceniu i rozwoju programistów, architektów i wszystkich innych osób w IT. Czy są zatem reguły które dotyczą modułowości? Tak oczywiście, są.
Divide and Conquer
Dziel i zwyciężaj, to najważniejsza reguła w informatyce, matka wielu innych reguł. Z niej wywodzimy 90% wszystkich informatycznych (i nie tylko) konstrukcji:
- podział kodu na instrukcje
- podział kodu na osobne pliki
- podział kodu na funkcje, klasy, metody
- podział kodu na pakiety, przestrzenie nazw
- dostarczanie oprogramowania w postaci modułów (dll, jar, …)
- paradygmat OOP i pochodne
- idea środowisk opartych o pluginy
- idea oddzielania deklaracji od implementacji
- architektura MDA, MSMaR, podział na model i runtime
- architektura platformy i rozszerzeń platformy
- architektura makro, mikro i nano serwisów
- … i wiele innych
Także idea składania systemów z mniejszych pod-systemów, usług czy serwisów (także mikro-serwisów) jest w pełni zgodna z tą regułą. Im większa aplikacja, większy system, tym bardziej wymaga podziałów. Tam gdzie pojawia się podział, tam naturalnie, na styku np. modułów, pojawia się konieczność komunikacji. Komunikacja taka może przyjmować proste formy w rodzaju synchronicznego wywołania metody z klasy publicznej z zewnętrznego modułu, powstają wtedy silne zależności (wołam tą metodę, muszę znać pakiet, jego klasę i kontrakt który one realizują). W wielu sytuacjach te silne zależności mogą generować problemy na tyle duże, że że ich nie akceptujemy. W związku z tym w wielu systemach dążymy do poluzowania (zniesienia) zależności pomiędzy modułami. Gdybyśmy mieli opisać sytuację idealną, w postaci całkowitego braku zależności, lista wymagań mogłaby przyjąć postać:
- moduły nic nie wiedzą o sobie, nie wiedzą nawet, że istnieją jakieś inne moduły
- istnieje skuteczny system komunikacji pomiędzy modułami
Te dwa wymagania wydają się być jednocześnie nie do zrealizowania. Skoro nie znasz rozmówcy i jego języka to jak z nim rozmawiać? Na szczęście znamy inne metody komunikacji niż konwersacja. Jedną z nich jest znana od stuleci idea rozsyłania wiadomości. Analogią w realnym świecie (o ile IT wciąż jeszcze jest nierealny) może być latarnia morska, która rozsyła „komunikat” w postaci strumienia światła we wszystkich kierunkach, nie wie czy tam gdzieś jest jakiś statek czy cała ich gromada która go odbierze, nie zna także sposobu odbioru (okiem, czy fotokomórką), ona wciąż wysyła komunikat „uwaga, jestem tu i tu jest ląd, port” i to wszystko. Przekładając to na nasze realia zobaczmy jak mógłby wyglądać związek modułu obsługi sprzedaży tworzącego paragony oraz modułu zajmującego się księgowaniem dokumentów finansowych. Pierwsze podejście mogło by wyglądać tak:
Moduł sprzedaży po wystawieniu paragonu wysyła „w przestrzeń” komunikat o treści „pojawił się nowy dokument sprzedaży” zawierający jego dane. I to byłby koniec działania tego modułu. Na razie idzie nam dobrze, z poziomu tego modułu nie mamy żadnej wiedzy co dalej, nie mamy więc żadnych zależności, na nic też nie czekamy. Niestety jednak moduł księgowy musiał posiąść wiedzę o tym, że taki właśnie komunikat może się „w przestrzeni” pojawić, odebrać go, rozkodować, zrozumieć i wykonać potrzebne u siebie działania. Niestety niniejszym okazuję się, że moduł księgowy uzależnił się od sprzedażowego. Co ważne, powstało pewne zobowiązanie modułu sprzedażowego co do chwili powstawania samej wiadomości i jej struktury, ktoś tego użył, to zaczęło stanowić kontrakt modułu sprzedażowego w stosunku do „przestrzeni”. Jakakolwiek zmiana w strukturze komunikatu, czy zmiana jego nazwy, mogłaby doprowadzić cały system do poważnych kłopotów. Zrobiło się ciężko i niebezpiecznie. Zauważmy też kto jest odpowiedzialny za zachowanie kontraktu. Jest to autor modułu sprzedażowego, a trudno od niego wymagać spojrzenia ogólnosystemowego, przecież „dzieląc i zwyciężając” wydzieliliśmy go jako byt niezależny i pozbawiony takich obciążeń jak sprawne działanie całego systemu, A tu taka odpowiedzialność, trochę to słabe. Drugie podejście pewnie wyglądało by tak:
Moduł księgowy mówi tak, nie będę łapał żadnych komunikatów od nikogo, nie chcę być od nikogo zależny, nie mam pojęcia kto co wysyła i jak ja mam to odebrać. Macie tu taki jeden komunikat „powstał dokument finansowy”, on ma takie pola wymagane i jak ktoś chce ze mną współpracować proszę takie komunikaty wrzucać „w przestrzeń” ja będę je łapał. Z punktu widzenia modułu księgowego jest dużo lepiej. Jednak moduł sprzedażowy znalazł się w dziwnej sytuacji, musi wiedzieć że istnieje moduł księgowy (zaczyna od niego zależeć) poznać strukturę komunikatu z tamtego modułu i zacząć go wysyłać. Oczywiście to od razu wymusza na module księgowym zachowanie kompatybilności kontraktu tego komunikatu, analogicznie jak poprzednio. Role się odwróciły ale problemy pozostały.
Myślę, że czytelnik pomyśli sobie teraz tak, skoro oba podejścia do komunikacji mają poważne problemy to pewnie żadnego z nich nie stosujemy. Jest dokładnie odwrotnie, na co dzień stosujemy powszechnie oba, a kwestia kto ma wyznaczać kontrakt, kto jest pasywny a kto aktywny pozostaje w gestii uzgodnienia przez architektów. Czy zatem istnieje inny „lepszy” system komunikacji? Co powoduje, że latarnia morska nie ma zależności od statków a statki od latarni (tylko od strumienia światła z pewnego konkretnego kierunku)? Postarajmy się to zdefiniować:
- Po pierwsze istnieje pewne medium fizyczne do przesyłania komunikatów (fotonów), reguły ich rozchodzenia się po liniach prostych, rozpraszania wraz z wzrostem odległości, itd. Medium to jest o wiele rzędów stabilniejsze od naszej latarni, wiadomo fizyka światła działa miliardy lat i będzie działać dalej, latarnia, no nie koniecznie. Medium to transportuje i przekształca źródłowy sygnał (światło z żarówki) w docelowy (strumień fotonów w oku marynarza) tak aby mógł zostać odebrany
- Po drugie istnieją dwie zasady w żegludze morskiej,
- że latarnia okresowo emituje strumień światła (kontrakt latarni)
- że jak światło (strumień fotonów) pojawia się okresowo w równych odstępach czasu w polu „widzenia” statku to jest to informacja o zbliżającym się lądzie, porcie itd. (kontrakt statku)
Dla każdej ze stron istnieje zatem pewien narzucony „kontrakt” który zna tylko ona i to jej wystarcza. Natomiast protokół i medium łączące stworzył ktoś inny („stwórca”, urząd komunikacji morskiej, …) a podmioty tej komunikacji używają kontraktów nie będąc od siebie zależni.
Przekładając to na nasz przykład sprzedaży i księgowania, wyobraźmy sobie, że żaden z tych modułów nie definiuje kontraktu, kontrakty pochodzą ze zbioru reguł świata w którym te moduły mają funkcjonować. Co jest tym „światem”? Jest to byt wyższy, platforma, centrum systemu, szkielet systemu, szyna danych, itp. :
W tej architekturze każdy moduł zna tylko siebie i zależy tylko od systemu/platformy, ma świadomość tylko jej, opiera się tylko o kontrakty i interfejsy które ona wystawia, żaden moduł nie wie nic o żadnym innym, nie zależy od innego modułu. Protokół transformacji jednego komunikatu w drugi jest ukryty i nie ma znaczenia dla obu uczestników komunikacji. Czyli pełen sukces (sic!). To jest ten moment kiedy mogło by się pojawić „ale” i jak się domyślacie musi się pojawić.
O ile dostarczenie czystej „fizyki ” (szyna/strumień/utrwalanie komunikatów) jest zadaniem dla platformy wyłącznie technicznym i do osiągnięcia bez większych problemów, o tyle „ale” w tym temacie polega na niełatwej odpowiedzi na pytanie:
skąd się mają brać w platformie kontrakty komunikatów?
W systemach dedykowanych o stałym zwięzłym zbiorze wymagań, źródłem powyższych może być zespół architektów systemu, bo wiadomo dokładnie co system robi i jakie komunikaty mogą się pojawiać. Jest to sytuacja wysoce komfortowa. A co z systemami dynamicznymi, w których zakres jest nie znany, jest zmienny w czasie i dynamicznie rozbudowywany poprzez moduły? Tu jest zdecydowanie trudniej, rozwiązaniem może być architektura w której każdy może stać się „architektem”, każdy może rozbudować „świat” o nowy zestaw komunikatów. Jak to rozumieć z praktycznego punktu widzenia? Kluczem do rozwiązania było by możliwość wystąpienia w dwóch rolach:
- dostarczyciela kontraktów w celu rozbudowy fizyki systemu
- dostarczyciela implementacji w celu zapewnienia funkcjonalności
Taki podział systemu na dwa światy interfejsów i implementacji zdaje się spełniać wszystkie wymagania jednocześnie nie posiadając w/w wad. Ale! W zadziwiająco szybki i sprytny sposób pominęliśmy jedno ze wcześniejszych zdań „Protokół transformacji jednego komunikatu w drugi jest ukryty i nie ma znaczenia dla obu uczestników komunikacji”. Pięknie, tylko:
skąd się mają brać w platformie reguły transformacji komunikatów?
Przecież pełny schemat powinien wyglądać tak:
”Router” i „Adapter” to coś co musi zostać zdefiniowane. Musi znaleźć się ktoś kto powie, jak „dodano dokument sprzedaży” to „powstał dokument finansowy” i wiadomo jak dane z jednego zdarzenia przenieść do drugiego. Tu z kolei odpowiedź może być podobna do poprzedniej. W systemach dedykowanych o stałym zwięzłym zbiorze wymagań zbiór transformacji komunikatów może być dostarczony przez architektów systemu. Jednak w systemach dynamicznych nie da się tego wykonać w tak prosty sposób. Po pierwsze, ten zbiór może być inny dla każdego wdrożenia systemu, dla każdego zastosowania. Po drugie, zbiór ten może być zmienny w czasie. Aby rozwiązać ten problem dla obu rodzajów systemów, należałoby zaczerpnąć z wzorca projektowego
Service Orchestration
Wzorzec ten zakłada właśnie istnienie w warstwie wyższej tworu który „zna” cel ogólny systemu (koncert orkiestry) i tak organizuje lokalne kontrakty poszczególnych modułów (skrzypek umie grać na skrzypcach, trębacz na trąbie) aby zrealizować cel. Patrzy na nich (odbiera zdarzenia), analizuje, dyryguje i koordynuje (wysyła zdarzenia). Fizycznie warstwa ta może przyjąć formę szyny integracyjnej ESB lub np. silnika procesów BPM, czy nawet kodu imperatywnego w jakimś języku programowania.
Można by w tym miejscu ogłosić sukces – mamy naprawdę ciekawą architekturę – to prawda, nie mamy żadnych w/w wad, jednak nie da się nie zauważyć, że przy takim podejściu wzrasta złożoność. Rodzi się pytanie, czy taki wzrost złożoności jest uzasadniony? Odpowiedź jest niezmienna i jak zawsze oczywista, to zależy. Dla każdego systemu może być inna i jak zawsze należy to dobrze przeanalizować osobno dla każdego konkretnego przypadku.
Na koniec, warto się jeszcze zastanowić jak powyższe rozważania odnoszą się do dwóch popularnych architektur, które to często używają metod komunikacji opartych na zdarzeniach.
Mikroserwisy – architektura mikroserwisowa nie wymusza posiadania jakiejkolwiek formy „translacji” komunikatów, dlatego, że jej implementacje zawierają zazwyczaj pewne uproszczenie i zgodę na jeden lub drugi model zależności czy nawet komunikację synchroniczną bez użycia zdarzeń. Nie jest to poważna wada szczególnie gdy architektura ta używana jest do zastosowania wewnątrz jednego konkretnego systemu, który realizuje jeden określony zbiór funkcjonalności i może nie łatwo, ale daje się nad zależnościami w jego wnętrzu zapanować. Oczywiście wzorzec orkiestracji (obok wzorca choreografii) z powodzeniem może być (i jest) używany także w tej architekturze.
SOA – architektura SOA swoim zakresem zastosowań sięga szerzej i zazwyczaj skupia się na łączeniu/integrowaniu różnych systemów składowych w jeden duży, złożony często o zasięgu korporacyjnym system, a tutaj realia i wymagania są znacząco inne. Każdy system składowy pochodzi z innego świata, i często orkiestracja jest jedynym skutecznym sposobem na sprawną organizację komunikacji. Brak zależności dostajemy tutaj „by design”, jest on stanem zastanym niosąc za sobą potrzebę komunikacji jako wyzwanie do zrealizowania. Oczywiście, można też iść drogą „choreografii” i któreś z systemów nauczyć rozmawiać z innymi (wprowadzając zależności), robi się tak w prostszych przypadkach (integracje P2P). Jednak rozwiązanie przedstawione w powyższym wywodzie, wydaje się skuteczniej adresować typowe potrzeby spotykane w SOA.