OSGi - Modularność | Part 1

Wpisem tym chciałbym zapoczątkować serię artykułów o technologii OSGi, technologii umożliwiającej wprowadzenie modularności do budowanych aplikacji i systemów na zupełnie nowym poziomie, technologii, która ma szansę odegrać kluczową rolę w świecie oprogramowania w całkiem niedalekiej przyszłości :)

OSGi jest specyfikacją rozwijaną przez OSGi Alliance grupę skupiającą kilkadziesiąt firm z całego świata. Głównym zadaniem grupy jest dostarczanie kolejnych specyfikacji OSGi i certyfikowanie ich implementacji.

Co właściwie znaczy OSGi? Skrót OSGi w pierwszych wersjach specyfikacji rozwijał się do Open Service Gateway initiative jednak w późniejszych wydaniach rozwinięcie to zostało zabronione ze względu na upowszechnienie się specyfikacji w miejscach, których pierwotnie nie przewidywała. Pierwsza wersja specyfikacji wydana w 2000 roku dotyczyła aplikacji sterujących fizycznymi urządzeniami i koncepcji automatycznego ładowania i instalowania sterowników do tych urządzeń. Kolejne wersje nie wiązały już się z konkretnym typem platformy sprzętowej co oczywiście wpłynęło na nieaktualność samego rozwinięcia nazwy. Jest ono więc na chwile obecną @Deprecated, mówiąc językiem developerów a OSGi nie znaczy po prostu nic. To w ramach ciekawostki.

Rozpoczynając dyskusje o OSGi zatrzymajmy się chwilę nad samym pojęciem modularności systemów, na pierwszy rzut oka terminu banalnego.

Modularność

Zacznijmy od definicji samego modułu, co to właściwie jest? Każdy ma na pewno swoją definicję tego terminu. Osobiście używam definicji jednostka implementacji przytoczonej w książce Software Architecture in practice co według mnie najtrafniej i najprościej opisuje to pojęcie. Innymi słowy moduł jest to po prostu pewien fragment kodu i na takim poziomie ogólności powinniśmy się zatrzymać. Modułem można więc nazwać cokolwiek, co umożliwia zgrupowanie elementów implementacji - jest nim metoda, jest nim także klasa, pakiet, podsystem a nawet cały system. Zakres znaczenia jest więc ogromny. Modularnością nazywamy z kolei technikę projektowania oprogramowania polegającą na podziale systemu na hermetyczne jednostki implementacji posiadające pewien poziom abstrakcji.

Zanim nawiążemy do OSGi proponuję prześledzić historię inżynierii oprogramowania oraz to w jaki sposób modularność pozwalała budować coraz bardziej skomplikowane aplikacje i systemy. W świetny sposób zostało to przedstawione w prezentacji Why OSGi zaprezentowanej na konferencji JavaOne 2011, która, nie ukrywam, zainspirowała mnie do napisania tego posta. Będzie to także dobry wstęp do technologii OSGi ukazujący główną jej zaletę.

Level 0 - Procedury

W dużym uproszczeniu wszystko zaczęło się w latach 50. i 60. ubiegłego stulecia, kiedy to zauważono, że grupowanie kodu w logiczne jednostki - procedury - umożliwia jego łatwiejsze zrozumienie, przyśpiesza rozwój, zwiększa modyfikowalność oraz niezawodność. Możliwe było to do osiągnięcia dzięki wprowadzonych przez procedury pewnych kluczowych aspektów:

  • abstrakcji - nie myślimy jak, tylko co robi dana procedura.

  • hermetyzacji - danych i kodu - nie mamy dostępu do danych przetwarzanych przez procedurę oraz szczegółów jej implementacji.

  • kontraktu dotyczącego formatu parametrów wejściowych oraz formatu danych wyjściowych co z kolei spowodowało wzrost niezawodności kodu poprzez wprowadzenie pewnych reguł implementacyjnych.

  • odpowiedniego podziału odpowiedzialności między procedurami

Dzięki abstrakcji programiści byli w stanie budować coraz bardziej skomplikowane systemy poprzez pozbycie się zbędnych detali, mogli skupić się więc na uproszczonym modelu rzeczywistości. Jeden z wykładowców Politechniki Gdańskiej, na której studiowałem bardzo trafnie określił, że abstrakcja jest matką postępu. Co do tego nie mam wątpliwości.

Hermetyzacja pozwalała z kolei ukryć informację. Umożliwiała izolację zmian wewnątrz procedury co znacząco zwiększyło modyfikowalność kodu. Wyeliminowała po prostu efekt domina czyli sytuacje, w której zmiana jednego elementu powoduje konieczność zmiany drugiego.

Jednak systemy wciąż rosły …

Level 1 - Moduły

Bardzo szybko okazało się, że samo grupowanie kodu w procedury przy ciągle rosnących rozmiarach aplikacji nie jest wystarczającym sposobem na osiągnięcie wysokiej jakości oprogramowania. Kod zaczęto więc organizować w moduły - jednostki grupujące procedury w logiczną całość. Pomysłodawcą modułów był David Parnas a sama technika została wykorzystana w języku Modula. Każdy moduł posiada swoją część publiczną oraz prywatną zwaną interfejsem. Poniżej przykład kodu napisanego w Modula-3:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
MODULE FirstModule;

IMPORT IO;

PROCEDURE PrintHelloWorld() = 
  BEGIN
    IO.Put("Hello\n");
    IO.Put("World\n");
    IO.Put("\n");
  END PrintHelloWorld;

PROCEDURE Sum(a: INTEGER; b: INTEGER): INTEGER =
  VAR sum: INTEGER;
  BEGIN
    sum := a + b;
    RETURN sum;
  END Calculate;

END FirstModule.

Wprowadzenie modułów umożliwiało wytwarzanie coraz bardziej skomplikowanych systemów z zachowaniem wysokiej modyfikowalności, niezawodności oraz łatwości wytwarzania. Wszystkie te cechy były możliwe do osiągnięcia dzięki:

  • abstrakcji - nie myślimy jak, tylko co robi dany moduł

  • hermetyzacji - nie mamy dostępu do części prywatnych modułu oraz szczegółów implementacji procedur

  • wprowadzeniu odpowiedniego podziału odpowiedzialności między modułami

Jednak systemy wciąż rosły …

Level 2 - Klasy

Lata 80. przyniosły szybki rozwój obiektowości, paradygmatu znanego już od lat 50. opierającego się na pojęciach klasy i obiektu. Rozwój ten przyczynił się do wprowadzenia kolejnego poziomu modularyzacji kodu - poziomu klas. Klasy są pojęciem zbliżonym do modułów jednak rozszerzają go o dodatkowe właściwości - polimorfizm, dziedziczenie, pojęcie instancji klasy (czyli obiektu) oraz modyfikatory dostępu do pól i metod.

Dzięki nowemu poziomowi modularyzacji możliwe było budowanie jeszcze bardziej skomplikowanych systemów z zachowaniem tych samych atrybutów jakościowych co w przypadku modułów. Cechy te możliwe były do osiągnięcia dzięki(tak samo jak poprzednio):

  • abstrakcji - nie myślimy jak, tylko co robi dana klasa

  • hermetyzacji - nie mamy dostępu do części prywatnych klas - pól oraz metod

  • wprowadzeniu odpowiedniego podziału odpowiedzialności między klasami

  • polimorfizmowi i dziedziczeniu, które przyśpieszały rozwój kodu oraz umożliwiały jego reużywanie

Jednak systemy wciąż rosły …

Level 3 - Pakiety

Lata 90. przyniosły rozwój języków C++ oraz Javy, które wprowadziły modularyzację kodu na jeszcze wyższy poziom - poziom pakietów. Pakiety jak wszyscy wiemy służą do grupowania elementów kodu o podobnym poziomie odpowiedzialności lub abstrakcji. Wraz z pakietami rozszerzono także modyfikatory dostępu o poziom package-private co umożliwiło wprowadzenie hermetyzacji na nowym poziomie abstrakcji.

Wyższy poziom modularyzacji jaki zaoferowały pakiety umożliwił wytwarzanie jeszcze bardziej skomplikowanych systemów z zachowaniem tych samych atrybutów jakościowych co w przypadku poziomu poprzedniego. Wszystko to było możliwe dzięki (tak samo jak poprzednio):

  • abstrakcji - nie myślimy jak, tylko co robi dany pakiet, za co jest odpowiedzialny.

  • hermetyzacji - nie mamy dostępu do prywatnych części pakietów - pól oraz metod oznaczonych jako package-private

  • wprowadzeniu odpowiedniego podziału odpowiedzialności między pakietami

Jednak systemy wciąż rosną !!! …

Co dalej??

No właśnie, co dalej? Co zrobić w przypadku gdy mamy podsystem składający się z kilkunastu pakietów, część pakietów chcemy udostępnić wewnątrz podsystemu jednak chcemy aby były niewidoczne dla korzystających z niego innych podsystemów? Z wykorzystaniem standardowych mechanizmów języka czyli pakietów i modyfikatorów dostępu niestety nie da się tego zrobić. Z pomocą przychodzi jednak specyfikacja OSGi wprowadzająca dodatkowy poziom modularyzacji - poziom bundle

Level 4 - bundle

Tym przydługim wstępem doszliśmy do sedna cyklu wpisów :) Czym jest więc bundle? W języku Javy jest to nic innego jak plain old JAR z jedną małą różnicą - JAR ukrywający domyślnie wszystko przed innymi JARami chyba, że dokona jawnego exportu swoich elementów. Bundle, który chciałby skorzystać z części innego bundle musi z kolei dokonać jawnego importu potrzebnych mu elementów. Domyślnie jednak nic nie jest współdzielone. Mamy więc odpowiednik modyfikatora package-private z tym że na wyższym poziomie abstrakcji.

Podsumowanie

Z uwagi na brak czasu w tej części to byłoby na tyle ;) Myślę jednak, że wyczerpałem temat historii modularyzacji oprogramowania oraz pokazałem, że OSGi ma swoją rolę do odegrania w tym zakresie co stanowić będzie dobre fundamenty pod kolejne wpisy. W kolejnym artykule omówię architekturę OSGi, budowę oraz cykl życia bundle’a. Uruchomimy sobie także pierwszy moduł OSGi w środowisku Karaf. Zapraszam już w krótce :)

Comments