Witaj na Zine.net online Zaloguj się | Rejestracja | Pomoc

Simon says...

Szymon Pobiega o architekturze i inżynierii oprogramowania
Przedstawiam się

Była już pierwsza notka, teraz czas, żeby się przedstawić.

Nazywam się Szymon Pobiega (co można chyba jakoś sprawdzić za strony pomocą zine-a). Jestem zeszłorocznym absolwentem Akademii Górniczo - Hutniczej w Krakowie. Mieszkam też w Krakowie. Z technologią .NET związany jestem od czterech lat. Od początku mój romans z .NET miał charakter profesjonlny (w kontraście ze zorientowanym na Java-ę programem studiów).

Nie jestem MVP (rozwiewając wątpliwości Tomka Smykowskiego).  MVC też nie jestem:P

W chwili obecnej zajmuję się głównie projektowaniem systemów OLTP tworzonych na zamówienie firm sektora finansowego. Poza tym prowadzę szkolenia (póki co wewnątrz mojej firmy); w planach mam uzyskanie tytułu MCT.

W wolnych chwilach zajmuję się realizacją projektów open-source, o których wktórce dowiecie się z notek na blogu.

Moje obecne zainteresowania, jeśli chodzi o zdobywanie wiedzy, koncentrują się na architekturze oprogramowania i roli architekta w procesie prodyukcji systemów informatycznych.

To chyba zestaw najważniejszych faktów o mnie. Po szczegóły możecie sięgnąć do linkedin/goldenline, albo zapytać w komentarzu:)

opublikowano 8 stycznia 2009 18:36 przez simon | 5 komentarzy

Inversion of Control w systemach zbudowanych w oparciu o obiektowy model domeny
Artykuł opisuje propozycję implementacji zasady Inversion of Control (IoC) w systemach OLTP zbudowanych w oparciu o obiektowy model domeny. Zakłada się, iż system ma być zbudowany zgodnie z ideą architektury cebulowej  kładącej nacisk na enkapsulację i izolację zmienności.

W kolejnych notkach będę starał się przybliżyć aspekty implementacyjne zaproponowanego rozwiązania. Mówiąc po ludzku - będą żywe przykłady.

Wprowadzenie


Architektura cebulowa została zdefiniowana przez Jeffrey-a Palermo w połowie 2008 roku. Idea jest więc całkiem nowa. Palermo w serii artykułów (1), opublikowanych na swoim blogu, zaproponował nieco inne, od klasycznego, podejście do warstw w aplikacji. Główną różnicą pomiędzy starym, a nowym, podejściem jest położenie nacisku na enkapsulację i izolację zmienności. Palermo zauważył, że klasyczne uzależnienie warstwy biznesowej od warstwy danych stoi w sprzeczności z faktem, iż część biznesowa aplikacji jest zwykle dużo stabilniejsza, jeśli chodzi o zmienność, niż część bazodanowa (która zmienia się wraz z nowymi technologiami). Efektem tej sprzeczności jest konieczność modyfikacji (a przynajmniej rekompilacji) warstwy biznesowej (oraz wszystkich warstw wyższych) w momencie zmiany warstwy dostępu do danych. Jest to coś, czego powinno się próbować unikać.

W zaproponowanym modelu cebuli warstwy położone w głębi charakteryzują się mniejszą podatnością na zmiany, niż warstwy płytsze. Zależności mogą być jedynie zwrócone do wnętrza cebuli. W ten sposób warstwa bazodanowa znajduje się na samej powierzchni, a model domeny – w centrum. Jak nietrudno zauważyć, jest to forma Odwrócenia Zależności (2), wymaga więc odpowiednich technik projektowych, takich jak:
aby móc w ogóle funkcjonować.

Artykuł ten jest konkretną propozycją zastosowania wspomnianych mechanizmów w architekturze cebulowej. Jej celem jest maksymalne uniezależnienie komponentów wszystkich warstw od wszelkich elementów infrastrukturalnych, w szczególności od infrastruktury realizującej zasadę odwrócenia sterowania.

Architektura systemu


Przykład omówiony w artykule jest zaczerpniętym z literatury (5)  systemem wykonanym w oparciu o architekturę cebulową z obiektowym modelem domeny w centrum.

Model domeny ma być wykonany według najlepszych praktyk związanych z tym wzorcem (6). W szczególności powinien on wykazywać wysoki poziom Persistence Ignorance (PI). Aby zrealizować to wymaganie architektoniczne, podjęta została technologiczna decyzja o wykorzystaniu biblioteki NHibernate, jak najpopularniejszego rozwiązania gwarantującego bardzo wysoki poziom PI.

W momencie wyboru tej technologii pojawia się pierwsze ograniczenie, które należy wziąć pod uwagę projektując architekturę Dependency Inversion. Jest to wymóg posiadania przez klasy modelu domeny bezparametrowego konstruktora. Ograniczenie to jest narzucone bezpośrednio przez konkretną technologię infrastruktury persystencji, jednak jeśli przyjrzeć mu się dokładniej, można zauważyć, że istnieje spora szansa, że inne technologie przedstawią dokładnie takie samo ograniczenie. W szczególności Entity Data Model, który w wersji 2.0 powinien gwarantować zbliżony do NHibernate poziom PI (7), prawdopodobnie wprowadzi podobne wymaganie.

Wracając na poziom abstrakcyjnej architektury, kolejną licząc od centrum cebuli, warstwą jest warstwa logiki aplikacji. Jest to miejsce, w którym żyją obiekty realizujące logikę, która nie jest cechą domeny problemu, ale cechą rozwiązania pewnego problemu istniejącego w tej domenie. Z bardziej technicznego punktu widzenia możemy powiedzieć, że w warstwie tej znajdują się obiekty enkapsulujące atomowe operacje realizowane na obiektach modelu domeny.

Kolejną warstwą w drodze ku powierzchni jest warstwa usługowa. Obiekty tej warstwy grupują i orkiestrują wywołania poszczególnych obiektów logiki aplikacji w celu realizacji pewnego określonego przypadku użycia (lub jego fragmentu).

Na samej powierzchni cebuli znajdują się najczęściej zmieniające się elementy systemu: warstwa prezentacji oraz warstwa dostępu do danych. Nie są one dla nas interesujące z punktu widzenia tego artykułu, dlatego zostaną pominięte w dalszej części rozważań.

Warstwa modelu domeny


Jak zaznaczono na wstępie, w tej warstwie należy liczyć się z narzuconymi przez technologię ograniczeniami. Oprócz wspomnianego wymagania tworzenia instancji za pomocą bezparametrowego konstruktora, istnieje jeszcze jeden problem. Jest nim brak jednego wspólnego API do wstrzykiwania zależności. Każdy dostępny na platformie .NET kontener Inversion of Control definiuje swoje własne API. W skład tego API mogą (tak, jak w przypadku Unity) wchodzić atrybuty, które są wymagane do wstrzykiwania zależności przez własności. Ten szczególny przypadek powoduje, że wykorzystanie podejścia Property Injection uzależniłoby model domeny od framework-u IoC.

Jako, że Constructor Injection jest także wykluczone, jedynym rozsądnym rozwiązaniem dla modelu domeny jest rezygnacja z wygodnego wstrzykiwania zależności na korzyść wzorca Service Locator. W literaturze teoretycznej (3) można znaleźć stwierdzenie, że wzorzec ten wymaga, aby komponenty były zależne od konkretnej implementacji wzorca. Praktyka, na szczęście, okazuje się jednak zupełnie inna. Na platformie .NET powstała inicjatywa Common Service Locator (CSL) (8), której efektem było stworzenie wspólnego dla wszystkich ważnych kontenerów IoC interfejsu lokalizatora usług (9). Dzięki temu zależność od CSL praktycznie nic nie kosztuje, w kontekście przywiązania do konkretnej technologii.

Jak jednak obiekty modelu domeny mogą dostać się do lokalizatora usług? Rozwiązaniem tego problemu wydaje się być statyczne udostępnienie referencji do obiektu lokalizatora (10). Dzięki temu może on być pobrany z dowolnego miejsca. Jeśli chodzi o konkretną implementację, to bardzo rozsądne wydaje się uczynienie tego kontekstowego lokalizatora specyficznym dla aktualnego wątku. W wypadku środowiska WWW właścicielem lokalizatora będzie obiekt obsługi żądania Web, natomiast w wypadku środowiska WCF – kontekst wywołania usługi.

Warstwa logiki aplikacji


Obiekty warstwy logiki aplikacji pozostają całkowicie pod kontrolą twórców systemu. Definicja architektury zakłada, ich instancjonowanie przez leżącą wyżej warstwę usługową. Nie ma więc żadnych przeciwwskazań dla wykorzystania metody Constructor Injection. Jest ona polecana (w przeciwieństwie do Property Injection) ze względu na fakt, że wstrzykiwanie zależności przez konstruktor nie wymaga – w większości framework-ów – żadnych specyficznych zabiegów (poza zadeklarowaniem samego konstruktora), które przywiązałyby nas do konkretnej infrastruktury.

Na osobne rozpatrzenie zasługuje specyficzny przypadek obiektu logiki aplikacji, jakim jest fabryka. Z doświadczenia autora wynika, że wzorzec fabryki (11) jest często spotykany w tej warstwie. Jest on niezbędny, kiedy produkcja obiektów realizujących pewien interfejs ma bazować na pewnym zestawie argumentów. Niestety kontenery IoC nie pozwalają zwykle na przekazywanie argumentów do metody rozwiązującej zależności., niezbędne jest więc zastosowanie jakiegoś dodatkowego mechanizmu.

[Edit(10.01.2009)]
Rejestrowanie klas fabrykowanych w kontenerze i wykorzystanie klasy-fabryki jedynie do mapowania argumentów metody fabrykującej na nazwy mapowań kontenera wydaje się dobrym pomysłem. Idealnym zaś byłoby rejestrowanie implementacji w kontenerze przez klasę fabryki, gdyż to w wyłącznie w jej zakresie obowiązków jest znajomość wszystkich konkretnych implementacji fabrykowanej abstrakcji.
[/Edit]

Niestety przeszkodą jest brak standardowego, dla wszystkich kontenerów, interfejsu rejestracji mapowań. Gdyby jednak taki standard istniał, optymalne rozwiązanie polegałoby na rejestracji mapowań w kontenerze podczas konstrukcji obiektu fabryki oraz późniejsze ich wykorzystanie w metodzie fabrykującej. Dodatkowym udoskonaleniem byłoby wykorzystanie nowej instancji kontenera, która byłaby „potomkiem” instancji głównej (kontenery potomne są wspierane przez większość framework-ów IoC). Konstruktor klasy fabryki tworzyłby kontener potomny, i to w nim rejestrował mapowania obiektów fabrykowanych. Dzięki temu inne obiekty korzystające z głównego kontenera nie miałyby dostępu do mapowań tej fabryki.

Ponieważ jednak w chwili obecnej nie dysponujemy standardem dla wspomnianej funkcjonalności, proponuje się zastąpienie jej prostszym rozwiązaniem: klasa fabryki pobiera z Service Locator-a zależności wymagane przez fabrykowane obiekty. Takie podejście sprawia, że klasy konstruowane za pomocą fabryki wyglądają zupełnie tak samo, jak te konstruowane bezpośrednio. Nie bierze jednak ono pod uwagę jednej ważnej kwestii: współczesne kontenery IoC, oprócz funkcjonalności wstrzykiwania zależności, oferują także możliwość wstrzykiwania aspektów. Ponieważ jest to bardzo istotna kwestia, niezbędne jest znalezienie sposobu na umożliwienie programowania aspektowego także w kontekście obiektów produkowanych przez fabryki.

Jako rozwiązanie proponuje się wykorzystanie czystej funkcjonalności wstrzykiwania, jaką oferuje każdy z kontenerów. Funkcjonalność ta może być udostępniona klasie fabryki jako interfejs IObjectBuilder z metodą

T BuildUp<T>(T plainInstance)

od którego klasa ta może być zależna. Fabryka po stworzeniu odpowiedniej instancji wykonuje metodę BuildUp, wstrzykując wymagane aspekty do instancjonowanego obiektu.

Niestety, nie istnieje jedna wspólna definicja funkcjonalności wstrzykiwania  do istniejących obiektów, jak to ma miejsce w wypadku Service Locator-a. Interfejs ObjectBuilder należy zdefiniować w systemie na własną rękę. Jego implementacja w kontekście dowolnego z głównych kontenerów nie powinna być problemem.

Warstwa usługowa


Obiekty warstwy usługowej także znajdują się pod pełną kontrolą twórcy systemu. Mogą więc być instancjonowane za pośrednictwem kontenera IoC dostępnego z poziomu warstwy interfejsu użytkownika. W tym wypadku, analogicznie jak w kontekście obiektów logiki aplikacyjnej, preferowanym sposobem wstrzykiwania zależności jest Constructor Injection. Argumentacja stojąca za wyborem tego podejścia jest podobna – celem jest jak największa niezależność od infrastruktury.
W warstwie usługowej nie występują typowo obiekty realizujące wzorzec fabryki, więc nie ma potrzeby zajmowania się tym specyficznym przypadkiem.

Podsumowanie


Powyższe sekcje dają pewien pogląd na optymalizację sposobu realizacji zasady Inversion of Control w różnych warstwach aplikacji. Wyszczególniliśmy trzy metody implementacji IoC:
  1. Obiekt wstrzykujący zależności i aspekty do istniejących obiektów – IObjectBuilder
  2. Wzorzec ServiceLocator
  3. Constructor Injection za pośrednictwem kontenera
Jeśli jednak przyjrzeć się liście operacji udostępnianych przez kontenery IoC, np. Unity, okaże się, że wszystkie trzy powyższe metody są wspierane przez odpowiednie funkcje kontenera. Problemem nie jest zatem określenie, jakiego charakteru mają być biblioteki IoC wykorzystywane w poszczególnych warstwach, ale to, jaki widok na ten sam kontener ma być udostępniony obiektom tych warstw.

Stwierdzenie tego faktu ma znaczenie o tyle ważne, że sprowadza problem do znanego problemu odpowiedniego umiejscowienia kontenera Investion of Control w warstwowej aplikacji. Istnieje wiele dobrych rozwiązań dla tego zagadnienia, a ich omówienie nie jest przedmiotem tego artykułu.
Podsumowując należy podkreślić, że nie istnieje jeden zawsze dobry i uniwersalny sposób realizacji zasady Inversion of Control. Z uwagi na ograniczenia o charakterze technicznym, najlepsze rezultaty można osiągnąć umiejętnie łącząc różne podejścia tak, aby wykorzystać ich najlepsze cechy. W szczególności należy zwrócić uwagę na fakt, że najbardziej widowiskowy sposób wstrzykiwania zależności, jakim niewątpliwie jest Property Injection, nie został w ogóle wymieniony jako zalecane rozwiązanie. Jest to efekt zarówno problemów natury technicznej (wprowadzanie zależności), jak i koncepcyjnej (możliwość utworzenia obiektu w niepoprawnym stanie).

Bibliografia


  1. Palermo, Jeffrey. The Onion Architecture : part 1. Jeffrey Palermo (.com). Lipiec 29, 2008. http://jeffreypalermo.com/blog/the-onion-architecture-part-1/.
  2. Martin, Robert C. The Dependency Inversion Principle. C++ Report. Maj 1996, 8.
  3. Fowler, Martin. Inversion of Control Containers and the Dependency Injection pattern. [Online] Styczeń 14, 2004. http://martinfowler.com/articles/injection.html.
  4. —. Patterns of Enterprise Application Architecture. s.l. : Addison-Wesley, 2003.
  5. Esposito, Dino and Saltarello, Andrea. Microsoft® .NET: Architecting Applications for the Enterprise. s.l. : Microsoft Press, 2008.
  6. Nilsson, Jimmy. Applying Domain-Driven Design and Patterns. s.l. : Addison-Wesley, 2008.
  7. Mallalieu, Tim. Initial POCO Design 1-Pager. Entity Framework Design. [Online] Czerwiec 24, 2008. http://blogs.msdn.com/efdesign/archive/2008/06/24/initial-poco-design-1-pager.aspx.
  8. Miller, Jeremy D. It's time for IoC Container Detente. Jeremy D. Miller -- The Shade Tree Developer. [Online] Sierpień 16, 2008. http://codebetter.com/blogs/jeremy.miller/archive/2008/08/16/it-s-time-for-ioc-container-detente.aspx.
  9. Tavares, Chris and Block, Glenn. Common Service Locator. [Online] http://www.codeplex.com/CommonServiceLocator.
  10. Block, Glenn. IServiceLocator a step toward IoC container / Service locator detente. Glenn Block. [Online] Październik 2, 2008. http://codebetter.com/blogs/glenn.block/archive/2008/10/02/iservicelocator-a-step-toward-ioc-container-service-locator-detente.aspx.
  11. Gamma, Erich, et al. Patterns: Elements of Reusable Object-Oriented Software. s.l. : Addison-Wesley, 1995.



opublikowano 7 stycznia 2009 19:48 przez simon | 5 komentarzy

Więcej wypowiedzi « Poprzednia strona