Dobra… Dobra? Dopasowana! Zanim podejmiesz w życiu jakąś ważną decyzję, to odpowiadasz sobie na masę pytań. Abstrahuję oczywiście od sytuacji kiedy tego nie robisz. Jako dziecko działałem w trybie akcja-reakcja. Ale załóżmy, że jesteśmy nieco bardziej doskonali. Jeśli bierzesz kredyt to sprawdzasz X rzeczy. Czy mogę w przyszłości nadpłacić bez prowizji, jakie jest oprocentowanie itp. Nie bierzesz kredytu we WProstychSlowach Bank Narodowy tylko dlatego, że ktoś powiedział, że tak jest właściwie.
Odpowiedz sobie na zajebi(@e ważne pytania
Podobnie jest z architekturą. Jeśli tworzysz program, najpierw dobrze jest się zastanowić co, poza opinią kolegów i typów z internetu, ma wpływ na Twoje wybory odnośnie tworzenia aplikacji. Czy silić się na jakieś wydumane style architektoniczne, czy może wystarczy stworzyć jeden pakiet, 10 klas i voila. A może trzy pakiety. A może moduł mavena. A może trzy moduły mavena i w każdym po iks pakietów. A czemu w ogóle maven? Czyżbym już podjął jakąś decyzję? Jeśli tworzysz aplikację z trzema kolegami i każdy z nich używał mavena, to prawdopodobnie nie będziesz na siłę teraz pakował się w inne narzędzie, skoro pierwsza wersja aplikacji ma być gotowa za trzy miesiące. O o o o o o… Chyba właśnie niechcący odkryliśmy dwa czynniki, które mogą wpłynąć na naszą decyzję odnośnie narzędzia do budowania projektu. Będzie to czas i umiejętności członków zespołu. Wiemy, że mamy mało czasu, a umiejętności zespołu w zakresie narzędzi do budowania projektu są spójne. Skoro tak, nie będę zaprzęgał gradle’a.
O tych czynnikach / drogowskazach (?) (ang. drviers) oraz o wizualizacji architektury na różnych poziomach możesz przeczytać we względnie krótkiej i przyjemnej książce Simona Browna Architecture for Developers, a kilka przykładów znajdziesz tu (są też w książce). Albo online, tu. Jeśli znasz lepsze tłumaczenie słowa driver, w znaczeniu coś co kształtuje, steruje/kieruje do czegoś, niż czynnik, to daj znać. 🙂
Jaką architekturę klikać
Przyjmijmy zatem, że architektura to odpowiedzi na pytania, które możliwe są po rozpoznaniu terenu, czyli po zidentyfikowaniu wspomnianych czynników. Czy w związku z tym, że na architekturę może wpływać sto milionów rzeczy, to czy można stwierdzić, że istnieje jakaś jedna najlepsza architektura? Byłoby ciężko. W każdej aplikacji będą zmieniać się różne rzeczy, różne rzeczy też będą niezmienne. Z reguły aplikacje różnią się też pod względem funkcji jakie wykonują. Czy będą powstawać nowe funkcje w przyszłości? A może jest mega szansa, że będą one się zmieniać w najbliższej przyszłości, bo ludzie biznesu jeszcze nie doszli do porozumienia? Eh, no jak widzisz nie jest to wszystko takie proste.
Architektura warstwowa
Masa aplikacji, które tworzymy w pracy polega na przyjęciu danych od użytkownika, wywołaniu, bądź nie, kilku akcji biznesowych i wykonaniu kilku operacji bazodanowych. Brzmi znajomo. Tworzymy kolejne GUI dla jednej lub kilku tabelek bazodanowych. No i okej, takie aplikacje też są potrzebne. Program tego typu raczej nie będzie złożony. Wiemy też, że w przyszłości nikt nie planuje większych zmian. Ot ewentualnie nowa nazwa kolumny, nowy kolorek, usunięcie kolumny. Poza prostą walidacją danych wejściowych od użytkownika i zapisie danych do bazy, nie wykonujemy żadnych innych akcji. Nie będziemy tutaj wyodrębniać nie wiadomo jak skomplikowanych abstrakcji, nie musimy nawet silić się na sexy enkapsulację zachowań, bo takich po prostu nie ma. Będziemy operować na danych, a nie na zachowaniach. Czy w takim razie architektura dwuwarstwowa spełni nasze oczekiwania i cele? Myślę, że tak. W zupełności. W tym przypadku architektura dwuwarstwowa jest dobrą architekturą. Architektura dwuwarstwowa składałaby się z warstwy, która udostępniałaby klasę kontrolera, który przyjąłby i zwalidował dane od użytkownika i przekazał wprost do warstwy odpowiedzialnej za operacje na relacyjnej bazie danych. Dwie warstwy, dwie odpowiedzialności. Nasza warstwa UI będzie uzależniona tym samym bezpośrednio od warstwy dostępu do danych. Uzależnienie, zarówno w życiu jak i w komputerach, może być jednak kłopotliwe.
Autonomia
Co jest fajne w życiu, skoro ustaliliśmy, że uzależnienie nie jest. No np.: autonomia. Prawo do decydowania o sobie i o swoich sprawach. Jeśli decyzja mojego sąsiada wpływa na moje życie lub życie mojej rodziny, to ja tak bardzo nie chcę. Nasuwa się pytanie co się stało, że jestem ze swoim sąsiadem tak mocno związany. Przecież jak się wprowadzałem 5 lat temu był dla mnie obcą osobą. Jednak gdy mieszkam ściana w ścianę z Teściową <3, to przecież wiem, że Ona chce dla mnie jak najlepiej i serce rośnie, gdy mogę być zależny od jej decyzji. Precyzyjniej: Twoja usługa posiada autonomię, wówczas gdy wprowadzana w niej zmiana nie powoduje powstania N zadań w backlogach innych aplikacji.
Czysta architektura
Ale wróćmy do architektury. Gdy nasz kod, weźmy klasę np.: ReportService, która oprócz jakichś tam biznesowych operacji na encji Report, wywołuje koniec końców metodę klasy odpowiedzialnej za wykonanie już tego Twojego inserta SQLowego (niech to będzie klasa SQLReportRepository), to jest spoko. Spoko jest dlatego, że klasę ReportService może kodować Jurek, a klasę SQLReportRepository może klikać Kasia i nie muszą się obawiać, że jedno będzie drugiemu wchodzić w drogę. Ale jak to. Przecież przed chwilą powiedzieliśmy, że ReportService wywołuje potem metodę klasy SQLReportRepository. To jakim cudem mogą być one niezależne. Czy to możliwe w ogóle? A możliwe.
Kluczem do zagadki jest odwrócenie zależności. Pisałem o tym o tu. W skrócie, dla przypomnienia: nasz ReportService znajduje się w warstwie aplikacyjnej, naszego najważniejszego modułu o nazwie reporting, który jest core’owym modułem, który realizuje logikę biznesową oraz aplikacyjną. Chcielibyśmy, aby był on niezależny od kodu, który nie realizuje przypadków biznesowych. No bo jeśli w ReportService użyjesz bezpośrednio klasy SQLReportRepository i następnie w niej coś zmienisz, to istnieje prawdopodobieństwo, że klasa ReportService przestanie działać. SQLReportRepository wykonuje operacje utrwalania danych, które biznesowymi zagadnieniami nie są. Odwrócenie zależności umożliwia nam uniezależnienie modułu reporting od reszty świata, także od warstwy zawierającej klasę SQLReportRepository. Będziemy wołali pośrednio z ReportService kod klasy SQLReportRepository za pomocą zdefiniowanego w domenie interfejsu. Co to oznacza – przeczytasz w poście o abstrakcji procesu oraz w poście o DIP.
Podsumowując
Pamiętamy o czynnikach, które wpływają na wybór architektury. Pamiętamy, że nie ma jedynego słusznego stylu architektonicznego. Architektura n-warstwowa jest fajna, bo każdy ją pewnie zna, każdy widział. Szybko się klika. Ale w pewnych okolicznościach może przestać spełniać Twoje potrzeby. Pamiętamy o autonomii. Może jeden moduł chcemy wdrażać niezależnie od innych? A może chcemy rozwijać dwa moduły niezależnie od siebie. Aaa może jeden moduł chcemy z jakichś powodów pisać w technologii X, a drugi w Y? Gdy zapewnimy autonomię, to wszystko będzie możliwe. Wprowadzanie zmian w poszczególnych modułach będzie przyjemniejsze i prostsze. Testowanie kodu również będzie fajne jak budowa z klocków. Przyda nam się tu znajomość takich terminów jak abstrakcja, enkapsulacja, odwrócenie zależności. Każdą decyzję musimy potrafić uzasadnić i do tego będą nam służyć wspomniane czynniki / drviery.