mar 072014 W kategorii Zawodowo

Onion Architecture

Zarys

Spodobała mi się sama nazwa „Onion Architecture” jak i to co pod tą nazwą przedstawił Jeffrey Palermo. Niby zasady znane od dawna, a jednak zostały one umiejętnie pokazane na nowo. Niestety na sieci ciężko znaleźć szkielet przykładowej aplikacji, która spełniała by podstawowe założenia.

W związku z tym na szybko zmontowałem takowy. Oto widok solucji przykładowej aplikacji, która nic nie robi, ale obrazuje zależności między projektami.

Solution App

Od razu rzuca się w oczy, że App.Domain nie ma żadnych referencji do innych projektów, a tym bardziej do żadnej zewnętrznej biblioteki. Takie podejście pozwala nam skupić się na modelu i implementacji logiki biznesowej w oderwaniu od wszelakiej infrastruktury.

Niektórzy pewnie spostrzegą Repository Pattern. Och… temat rzeka… :) Ucinam go w tej chwili. Miałem różne przemyślenia na ten temat. Szukałem, czytałem. Ile rozwiązań tyle wątpliwości. Za każdym razem pozostawiały one pewien niedosyt wynikający ogólnie z niedomówień. Zastosowanie Repository Pattern jest bardzo dobrym pomysłem, ale tylko w odpowiednim miejscu. Tyle już było o tym pisane… także i ja pozwolę sobie na swoje ostatnie przemyślenia w którymś z kolejnych wpisów. :)

Solution UI

Wracając do naszego szkieletu. Powyżej aplikacja UI z rozwiniętą sekcją referencji. Tutaj widać, że nie ma referencji ani do App.DependencyResolution ani do App.Infrastructure. Kod z tych projektów potrzebny jest do tego, aby podczas uruchomienia aplikacji zainicjowały się odpowiednie części systemu oraz do tego, aby obsłużyć działanie naszej logiki biznesowej w tych obszarach, gdzie musimy się odwołać do zewnętrznych zasobów takich jak baza danych, serwer pocztowy, itp.

UI odpowiedzialne jest, jak sama nazwa wskazuje, za udostępnienie interfejsu użytkownika pozwalającego na interakcję z naszym modelem oraz za wyświetlanie danych, które ten model przetworzył i zrzucił do zbiornika danych. Ech… strasznie koślawo to brzmi, ale napisałem tak celowo. Naszym zbiornikiem danych nie musi być baza danych, a tym bardziej relacyjna baza danych. Mogą to być zwykłe pliki, baza dokumentowa, web-serwisy, albo kto tam sobie coś jeszcze innego wymyśli. Co więcej, możemy trzymać dane w wielu różnych technologiach jednocześnie, a nasz Domain Model powinien żyć w nieświadomości.

Skąd dane do widoków?

Spytacie pewnie skąd zatem wziąć dane do widoków. Pytanie podstępne i odpowiedź niestety też będzie dwojaka. Otóż w większych projektach niż te, z którymi miałem/mam przyjemność pracować całkowicie odseparowałbym warstwę dostępu do danych poprzez jakąś warstwę abstrakcji. I nie, nie było by to Repository. Więcej o tym innym razem.

Teraz odpowiedź na miarę projektu mniejszego: do widoków dane ciągniemy łącząc się bezpośrednio do bazy. Kropka. Jeśli aplikacja była by oparta o bazę dokumentową to skorzystałbym ze wszystkich dobrodziejstw API dostępowego do takiej bazy. Do bazy relacyjnej – NHibernate i wszystko to co daje nam za free. Innymi słowy w projekcie UI dodajemy referencję do NHibernate.dll.

Czy to nie jest „over-architected”?

Na pierwszy rzut oka tak się wydaje. Po pierwsze nie rozmawiamy tu o małych/prostych aplikacyjkach. Chcemy zbudować podwaliny pod projekt średniego bądź nawet dużego kalibru, który będzie zawierał trochę bardziej rozbudowaną logikę biznesową. Nie mówimy tu też o projektach klasy Enterprise. Po pierwsze takich projektów robi się duuuużo mniej niż tych średnich. Po drugie sam nie mam większego doświadczenia w tak ogromnych przedsięwzięciach. Nie poruszam także tematu CQRS. Więcej o tym możecie poczytać na blogu Szymona. CQRS jest fajne, ale nie we wszystkich projektach.

Po drugie, faktycznie start od zera jest mozolny i wymaga pewnego obsadzenia projektu w kod infrastrukturalny, dzięki któremu aplikacja w ogóle ruszy. Powiem jednak stanowczo, że robi się to raz na początku, a z czasem przestaje się w ogóle tam zaglądać. Po prostu wszystko działa, a my możemy zająć się tym co najbardziej wartościowe.

Gdzie jest kod?

Nie wiem czy jest jakikolwiek sens udostępniać czyste projekty, w których nic nie ma. W kolejnych wpisach na pewno nie zabraknie przykładowych listingów, ale od czego jest gist. :)

Kod aplikacji znajduje się na githubie. https://github.com/dario-l/OnionExample

Co dalej?

Dalej proponuję, abyśmy bliżej przyjrzeli się naszej aplikacji UI. Tu jest punkt styku użytkownika z aplikacją. To UI generuje dane, które nasz model będzie przetwarzał. To użytkownik będzie uruchamiał procesy, których zachowanie będzie zaimplementowane w domenie.

Obrazek pochodzi ze strony http://www.planetgeek.ch/2013/11/26/chop-onions-instead-of-layers/

  • Pingback: dotnetomaniak.pl

  • Wojciech Gomoła

    Brakuje mi jednej kwestii czy repozytorium wstrzykujemy do klasy User? Czy pobieramy z IoC w Kontrolerze? Mnie aż korci żeby do Domain dodać „UserService” do którego takie repo byłoby wstrzyknięte przez konstruktor.

    • Dariusz Lenartowicz

      Do klas entity z zasady nie wstrzykujemy. Nie wstrzykujemy serwisów, gdyż te służą do koordynowania zachowań na więcej niż jednym obiekcie domenowym, a więc mielibyśmy pomieszanie odpowiedzialności. Nie wstrzykujemy też repozytoriów, gdyż poniekąd obiekt taki nagle stał by się odpowiedzialny za pobieranie danych, a nie to jest jego rolą.

      IoC i kontroler to inna warstwa.

      Natomiast dobrze Cię korci. W domenie, czyli w części biznesowej jak najbardziej stosowne jest użycie serwisu (będzie to Domain Service), gdzie w konstruktor możemy mieć wstrzyknięte repo. :)