Większość z nas tworzy oprogramowanie z myślą, że będzie ono łatwo rozszerzalne. Ale co jeżeli musisz pozwolić na rozszerzenia zewnętrznemu zespołowi albo komuś całkowicie obcemu spoza organizacji? Jak rozwiązać taki problem? Z pomocą przychodzi gwiazda dzisiejszego wpisu – architektura mikrojądra!
Cześć
Architektura typu mikrojądro to już trzeci wpis z mini-serii blogów o architekturach aplikacyjnych. W poprzednich wpisach mogłeś przeczytać o architekturze warstwowej i architekturze sterowanej zdarzeniami.
W dzisiejszym artykule:
Czym jest architektura mikrojądra?
Architektura mikrojądra lub inaczej architektura wtyczek stosowana jest najczęściej kiedy oprogramowanie musi być w stanie dostosować się do zmieniających się wymagań systemowych.
Mikrojądro składa się z dwóch elementów: jądra (rdzeń systemu) oraz wtyczek (pluginów). Rdzeń w najprostszych słowach jest jakimś podstawowym elementem (czasami podstawową funkcjonalnością) systemu. Zawiera on minimalną ilość kodu jaki jest potrzebny do uruchomienia systemu. Reszta leży na barkach wtyczek. Każda wtyczka to całkowicie oddzielna logika biznesowa. Z założenia, wtyczki nie powinny wiedzieć o sobie nawzajem (nie zawsze się tak dzieje) i być całkowicie niezależne. Każda z wtyczek dostarcza konkretną funkcjonalność biznesową do systemu. Innymi słowy – rozszerza go.
Jądro musi w jakiś sposób wiedzieć jakie wtyczki są dostępne oraz jak się do nich dostać. Do tego celu służy rejestr. Zawiera on informacje o wtyczkach oraz ich kontrakt. Kontrakt to zasadniczo dane wejściowe i wyjściowe – pozwala on na wymianę danych pomiędzy rdzeniem i wtyczką. Nie zawsze będzie tak, że wtyczka domyślnie wspiera zdefiniowany kontrakt. Stosuje się wtedy wzorzec projektowy adapter, który odpowiednio zmapuje funkcjonalność.
Mikrojądro można traktować trochę jak wzorzec metody szablonowej. Mówi ono co ma się zadziać i w jakiej kolejności, natomiast jak to się zadzieje jest już zależne od użytych wtyczek.
Przykłady
Najlepszymi przykładami (do wytłumaczenia) są: IDE, edytory tekstu i przeglądarki.
IDE. Zapewnia nam podstawową funkcjonalność – widzimy strukturę projektu, możemy pisać kod i go uruchamiać. IntelliJ wspiera różne języki JVM’owe do kompilacji oraz uruchamiania. Jest to właśnie element wtyczki. Jądrem systemu jest kompilacja i uruchomienie, natomiast wtyczką konkretny język. Ich kontraktem wyjściowym jest zapewne byte code. Rejestrem w tym przypadku będzie język jaki wybraliśmy przy tworzeniu projektu.
Przeglądarki. Jądrem systemu w tym przypadku jest przeglądanie witryn internetowych. A wtyczki? Istenieje duże prawdopodobieństwo, że sam posiadasz zainstalowanych kilka(naście) różnych wtyczek. Wszystko to rozszerza główną funkcjonalność – przeglądanie witryn internetowych – uzupełniając ją o dodatkową logikę.
Spring Boot. Nie jestem w 100% pewny czy jest to trafiony przykład ale powiem jak ja to widzę i rozumiem. Spring przy starcie aplikacji czyta wiele plików konfiguracyjnych, rejestruje bean’y, wykonuje prace zarejestrowane w post processorach i innych. Jeżeli spojrzymy na to z punktu widzenia mikrojądra to można odebrać wrażenie, że jądrem w tym przypadku jest start aplikacji. Wtyczki są to bean’y, post processory i inne udogodnienia, które się dzieją na starcie. Rejestrem może być plik konfiguracyjny, XML lub adnotacja. Kontraktem jest natomiast interfejs.
Nie jestem w 100% pewny czy ostatni przykład jest dobry. Jeżeli znasz jakieś przykłady z życia wzięte (czytaj framework) to podziel się w komentarzu 🙂
Wady i zalety architektury
Zalety:
- Testowalność – osobne, niezależne wtyczki, które można przetestować w izolacji. Samo jądro również jest minimalną częścią systemu, które nie powinno być uciążliwe w testowaniu (+ można wykorzystać mockowanie).
- Konfigurowalność – można na różne sposoby skonfigurować ten sam proces, np. kompilacja projektu przy użyciy Javy, Kotlina, Groovy’ego czy Scali.
- Rozszerzalność – łatwo dodać nową wtyczkę, która rozszerzy funkcjonalność systemu. Przykład: powstaje nowy język JVM’owy – większość dobrych IDE (patrz IntelliJ) nie będzie miała problemu aby go wesprzeć.
Wady:
- Skalowalność – mikrojądro pracuje w obrębie jednego modułu. Dokładając kolejne wtyczki może to być wąskie gardło tej architektury w kontekście wydajności.
- Złożoność – jest to skomplikowany styl architektoniczny. Mamy dużo elementów do ogarnięcia – rejestr, kontrakty, konfiguracja wtyczek – to wszystko sprowadza się do wysokiego progu wejścia.
Podsumowanie
Architektura mikrojądra z założenia jest prosta. Nie jest ona natomiast łatwa. Przy większych projektach (których produkt ma żyć długo) – a zazwyczaj właśnie takie projekty powinny z niej korzystać – jest ona ciężka do ogarnięcia dla nowej osoby. Jej główną zaletą jest elastyczność i to ten driver architektoniczny powinien przemawiać za wyborem tej architektury. Kluczowe jest aby przed jakąkolwiek implementacją mieć wizję i plan – po prostu trzeba wykonać dokładną analizę projektu.
W przypadku aplikacji opartych na produktach (IDE, edytory tekstu, przeglądarki, …) architektura mikrojądra wydaje się być świetnym kandydatem. Pozwala ona na udostępnianie dodatkowych funkcji użytkownikowi w zależności od jego preferencji. Dodatkowo, dzięki kontraktom nie musisz sam wytwarzać każdej nowej wtyczki a zamiast tego użyć community zbudowanego wokół produktu :).
Źródła:
- Software Architecture Patterns (Mark Richards) – Chapter 3. Microkernel Architecture
- What is a microkernel architecture, and is it right for you?
- Software Architecture Patterns — Microkernel Architecture
- Dynamic component binding made easier – An easy to use Microkernel to help reap Contract-First-Design benefits in .NET programs
Za tydzień
Kontynuujemy serię o archiekturze! Kolejna będzie architektura Hexagonalna.
Architektura mikrojądra lub inaczej architektura wtyczek stosowana jest najczęściej kiedy oprogramowanie musi być w stanie dostosować się do zmieniających się wymagań systemowych.
Nazwa „architektura mikrojądra” jest kompletnie myląca, bo ten termin powszechnie odnosi się do systemów operacyjnych i znaczy zupełnie co innego – https://pl.wikipedia.org/wiki/Mikroj%C4%85dro Żaden z popularnych systemów operacyjnych nie używa mikrojądra, mimo że każdy z nich ma sterowniki (czyli pluginy). Z mikrojądrem w systemach operacyjnych chodzi o pluginy, ale też o coś więcej: chodzi też o to że każdy plugin jest odizolowany w oddzielnym procesie, zbudowanym tak, że że awaria jednego pluginu nie psuje reszty systemu. Np. w świecie Javy taką architekturą z „mikrojądrem” mogą to być aplikacje Spring Bootowe podłączone do kolejek Kafki. Jedna aplikacja wrzuca coś do kolejki, inna czyta. Awaria… Czytaj więcej »
Generalnie architektura mikrojądra wywodzi się z systemów operacyjnych,
Uważam, że samo użycie architektury powinno być podyktowane wymaganiami. Słusznie wskazałeś: awaria jednego pluginu nie psuje reszty systemu – pewnie jest też jeden z driverów architektonicznych który należy rozważyć przed rozpoczęciem implementacji.
Na ten moment nie mam doświadczenia z pracy z tą architekturą, więc nie chcę się wdawać w dyskusję. Mam nadzieję, że kiedyś będę mógł powrócić do tematu z większym bagażem doświadczeń 🙂
Hej, chyba przyczepiłbym się do założenia testowalności pluginów. Zasadniczo pluginy nie potrafią istnieć bez mikrojądra, więc nie da rady ich sensownie przetestować. Dodatkowo każdy nowy plugin zmienia funkcjonowanie całej aplikacji, co wymusza odpowiednie pisanie testów dla całej aplikacji z „zamockowanymi” pluginami, co z kolei doprowadza do tego, że bardzo trudne i karkołomne będzie pisanie testów do aplikacji z użytymi pluginami. Zakładając sytuację kiedy istnieje wiele wersji różnych pluginów wpadamy w ciąg niekończącego się przepisywania testów tak, aby mieć pewność, że nie zepsuliśmy całego flow aplikacji. Oczywiście możemy wyjść z założenia, że testujemy jednostkowo wszystkie komponenty, ale moim zdaniem największa wartość… Czytaj więcej »
Słusznie. Jeżeli będziemy chcieli przetestować działanie aplikacji jako całości (przy użyciu wszystkich pluginów) to możemy wpaść w pułapkę mockowania i ciągłego przepisywania testów bo jakaś mała rzecz się zmieniła. Przy testach integracyjnych mikrojądra skupiłbym się na testowaniu działania z konkretnym pluginem zamiast całą konfiguracją. Przykład: masz jądro, którego zadaniem jest przetwarzanie video. Elementami jądra są np. konwersja video i zapis. Pluginami dla konwersji może być format MP4, MKV a dla zapisu: zapis do google drive’a, zapis na dysk, itp. Teraz zamiast skupiać się na testowaniu całości lepiej skupić się na przetestowaniu działania konkretnego pluginu z mikrojądrem – czy wykonuje swoje… Czytaj więcej »