Premium SOLID dla praktykujących czystą architekturę

utworzone przez | 16/02/2020 | Architektura, SOLID w czystej architekturze

Praktykowanie zasad SOLID nie wymaga tajemnej wiedzy. Da się je stosować do projektów każdej wielkości. Nie generują narzutu i nie skutkują tym, że liczba klas w projekcie będzie rosła w tempie wykładniczym. 

Konkretny przypadek biznesowy i niewymierne korzyści płynące z zasad SOLID. Za kilka akapitów zobaczysz je na własne oczy.

Jeśli jeszcze tego nie zrobiłeś, to zajrzyj na chwilę do artykułu  “Community Edition SOLID w służbie czystej architektury”. Dowiesz się dlaczego w ogóle ten SOLID to jest tak ważny.

Pogromcy mitów

Dawno dawno temu, w odległej galaktyce…. wydawało mi się, że zasady SOLID to takie terminy, o których wolno rozmawiać tylko starszym programistom i architektom. Tym mądrzejszym, którzy swoje myśli przelewają na diagramy UML, a nad tymi UML-ami odprawiają magiczne zaklęcia “solid-solid-solid-solid” 🧙‍♂️.

Drugim mitem było to, że SOLID aplikuje się wyłącznie do zaawansowanych architektur wielkich projektów.

Niemądry ja wtedy – żadne magiczne sztuczki nie są potrzebne.

Zasady są łatwe do zrozumienia, co możesz sam stwierdzić po lekturze poprzedniej części – Community Edition SOLID. Za chwilę przekonasz się o tym, jak z powodzeniem je stosować. 

Był sobie startup

Pewien czas temu Tomek, uruchomił startup Mozartify. Wymyślił w nim nowatorskie podejście do sposobu rozliczania, ale o tym w innym poście. Tomek pozyskał fundusze od inwestorów, zbudował i wystawił publicznie prototyp. Użytkownicy zaczęli się rejestrować, baza rosła i rosła aż…

Za dobrze nie jest dobrze

Biznes spotyka pierwsze problemy. Nastąpił niebywały wzrost liczby użytkowników usługi. Relacyjna baza danych ledwo dawała radę. Pojawiły się problemy wiszących zapytań, nieudanych anulowanych transakcji. Wskaźnik nowych rejestracji spadł. Inwestor zaczął zadawać niewygodne pytania.

Wyzwanie skali i rozwiązanie

Z pomocą przyszedł Janek (tech lead, rzemieślnik oprogramowania) wraz ze swoim zespołem. Zdecydował, że to odpowiedni moment na wymianę silnika bazodanowego na NoSQL, który posiada wbudowane równoległe wykonywanie obliczeń, a tym samym daje się skalować. Wybór padł na ElasticSearch i zastosowanie go w operacjach odczytu (odciążenie mechanizmu kontroli wersji z transakcyjnym relacyjnym PostgreSQL).

SOLID-ne skalowanie

Prototyp Mozartify operował na dokładnie jednej klasie (PostgreSqlStorage) odpowiedzialnej za operacje na bazie danych, konkretnie na użytym dialekcie PostgreSQL.

Single Responsibility Principle (SRP)

Klasa PostgreSqlStorage powstała zgodnie z założeniem SRP. Tylko jedna kategoria wymagań biznesowych miała wpływ na zmianę kodu wewnątrz tej klasy – sposób zapisu lub odczytu danych.

To bardzo dobre podejście w początkowej fazie projektu. Zamiast drobnicować kod na wątpliwie potrzebne klasy, trzymamy wszystkie operacje jako jedną całość.

Klasa PostgreSqlStorage pozostała by w pierwotnej formie, gdyby nie wyzwanie skali.

Interface Segregation Principle (ISP)

Janek zauważył, że klasa PostgreSqlStorage jest tzw. niejawnym interfejsem. Niejawny interfejs to skrót implementacyjny. Zamiast tworzyć abstrakcję i ją implementować, po prostu piszesz konkretną implementację. Działa do czasu, aż faktycznie potrzebujesz abstrakcji. A wtedy…

Jeden raz interfejs. Zamawiam

Pierwszą operacją jaką wykonał Janek, była ekstrakcja faktycznego interfejsu Storage.

Zobacz jaki kozak! IDE zrobiło to za niego. Refactor > Extract > Interface…

Co doprowadziło do zależności:

Jeden interfejs to za mało

Ale to nie koniec. Kolejnym krokiem był podział interfejsu na abstrakcje opisujące operacje odczytu i zapisu(ReadOpStorage, WriteOpStorage).

Powstanie ReadOpStorage, WriteOpStorage to właśnie segregacja interfejsu. Kiedy pojawi się nowe kosmiczne rozwiązanie do jeszcze szybszych operacji odczytu, wystarczy że dostarczymy implementację zgodną z interfejsem ReadOpStorage. Nic już nie wymusza implementacji nadmiarowych operacji zapisu.

Tym jednym strategicznym posunięciem Janek zapewnił zgodność kodu z dwiema kolejnymi zasadami.

Open Close Principle (OCP)

Wydzielenie interfejsu Storage otworzyło drogę do nieograniczonej rozbudowy. Pojawi się nowa baza danych? Nie ma sprawy, napiszesz niezbędną implementację. To właśnie Open, czyli możliwość elastycznego modyfikowania zachowania klasy i miejsc, w których została użyta.

Close polega na tym, że nie ma potrzeby dotykania kodu już istniejącego. To z kolei minimalizuje ryzyko błędów i niepowodzenia w projekcie (zanotować: ważny argument do rozmów z Project Managementem 🤓).

Janek to bystry obserwator. Zauważył, że po zaaplikowaniu OCP, może w końcu pokryć Mozartify testami.

Spójrz na kod 2 klas, które mu to umożliwiły: RamStorage i FileStorage. Dopisał je i wykorzystał je do napisania testów jednostkowych i behawioralnych. Persystencja stanu między żądaniami, nie potrzebuje już mockowania i interakcji z silnikiem bazy danych 💪.

A tutaj nagroda za ciężką pracę. Efekt uruchomienia narzędzia behat do testów behawioralnych. 100% zadowolenia😀.

Liskov Substitution Principle (LSP) 

Wspomniałem, że zastosowanie ISP otworzyło drogę do zgodności z dwiema zasadami. Zasada podstawień jest właśnie tą drugą. 

Czy na pewno LSP to taka oczywista zasada?

Kiedyś myślałem, że LSP to prosta i automatycznie stosowana zasada OOP. Piszesz interfejs (abstrakcję) strzelasz na jej podstawie konkretną implementację i ogień 🔥. Definiujesz abstrakcje jako typy przyjmowane i zwracane przez metody i gotowe!

No właśnie nie. LSP naprawdę łatwo naruszyć. Janek wyjaśnił to swojemu zespołowi na podstawie interfejsu Storage. Popatrz na poniższy kod.

Metoda createNewTenant z pustym ciałem = nic się nie dzieje. Metoda addPackage = wyjątek rzucony użytkownikowi prosto w twarz. Pisząc klasę UseCaseImpl zdecydowanie nie spodziewasz się takiego zachowania.

Dziedziczenie i polimorfizm

Teoria dziedziczenia zezwala na zmniejszanie restrykcji, ale w żadnym wypadku ich zwiększanie. Jeśli abstrakcja ma metodę prywatną createNewTenant, to klasa dziedzicząca z powodzeniem może rozszerzyć wachlarz tego co oferuje swoim klientom i zmienić modyfikator dostępu na public (zmniejszyć restrykcje). Mówiąc prościej: jeśli coś miałeś, to nie możesz nagle przestać tego mieć, ale możesz mieć lepiej i więcej.

Nie wolno kłamać!

Co dzieje się w metodzie createNewTenant? Nic. Zawiera jedynie definicję i komentarz /* do nothing*/ (instrukcję pass w Python). Miałem coś mieć, a jednak nie mam. Kod kłamczuszek! Takie przypadki raczej nie przejdą przez code review. Przynajmniej nie powinny.

Te niedobre wyjątki

A metoda addPackage i wyjątek, co tam jest źle? – spytał Janka jeden z developerów. Odpowiedź znów odnosiła się do teorii dziedziczenia miałem – nie mam.

Na wyjątek patrz jak na restrykcję. Czy widzisz ją w abstrakcji Storage? Nie, brak adnotacji @throws. Zatem implementując metodę UseCaseImpl::execute nie przyjdzie ci do głowy, aby dodać blok obsługi wyjątków try-catch. W realnym świecie wyjątek wystąpi wskutek bardziej wyrafinowanego kodu niż na przykładzie, ale wystąpi. Zobaczysz go w logu aplikacji, może w Sentry, a może jako palący ticket od użytkownika w bugtrackerze.

Dodaj koniecznie sprawdzanie obsługi wyjątków w kontekście LSP na check-listę code review. 

Dependency Inversion Principle (DIP)

Na koniec zasada odwrócenia zależności. Czyli kompilacja tego, co Jankowi udało się zbudować, od momentu kiedy pospieszył z pomocą Tomkowi.

Podstawy dotyczące zasady DIP i ideę jej przyświecającą znajdziesz w artykule: “Community Edition SOLID w służbie czystej architektury”.

Solidny rysunek

Popatrz na rozwiązanie dostarczone przez zespół Janka. Co widzisz?

Przepływ sterowania w aplikacji

Konkretny przypadek użycia (UseCaseImpl) wykonuje operacje na bazie danych komunikując się z fasadą (StorageFacade). Dlaczego z fasadą? UseCaseImpl to fragment domeny aplikacji. Chce jedynie wykonać operację biznesową. Nie interesuje go czy pod spodem będzie operacja zapisu czy odczytu. To rola fasady. 

Z kolei fasada (StorageFacade) nie martwi się czy korzysta z ElasticSearchStorage czy PostgreSqlStorage.  Działa w oparciu o to, co opisują abstrakcje czyli: ReadOpStorage i WriteOpStorage.

Dalej jest granica – gruba niebieska linia, poniżej której znajdują się konkretne szczegóły implementacyjne.

Zależności w kodzie źródłowym

Klasy ElasticSearchStorage i PostgreSqlStorage to wyspecjalizowany kod, obsługujący konkretne silniki bazodanowe. Jakie zależności je opisują? Interfejsy. Obydwie klasy obiecują spełnić kontrakty zdefiniowane w tych interfejsach.

Zwróć uwagę na zwroty strzałek wskazujących na interfejsy. Groty strzałek dotyczących zależności (zamknięte, puste w środku) są skierowane przeciwnie niż te, które określają przepływy sterowania.

DIP – Zależności są odwrócone względem sterowania

Właśnie poznałeś tajemną genezę powstania nazwy dla zasady DIP.  Zależności klas są odwrócone względem sterowania. I dokładnie o to chodzi w DIP.

Janek doskonale wiedział, dlaczego dokonał odwrócenia sterowania.

Czysty zysk

Kod zorganizowany zgodnie z zasadą DIP jest niezależny od konkretnych implementacji. Operuje na abstrakcjach. Jest otwarty na nowe sterowniki.

Ta otwartość pozwala na wymienność. Potrzebujesz MySQL? Nie ma sprawy. Zaimplementuj jedynie odpowiedni interfejs i podmień konfigurację kontenerów dependency injection frameworka.

Wymienność otwiera drogę do testowalności, bez uprawiania kaskaderki na mockach i zaślepkach. Piszesz prosty RamStorage albo FileStorage i masz zabawki do testowania unitów lub zachowań (BDD). Ty decydujesz! 🍾

Podsumowanie

W SOLID nie chodzi o to, aby położyć zasady na biurku i wpasować swój kod w jego ramy. SOLID należy traktować jako checklistę i rozpatrywać wszystkie 5 zasad w całości. 

SOLID to podstawy nawigacji w kodzie obiektowym. Dzięki nim będziesz mógł:

  • poruszać się w kodzie prawdziwie obiektowym, jak po książce z rozdziałami i podrozdziałami (SRP), z opcją pójścia na skróty przez indeksy i skorowidze (ISP)
  • rozbudowywać istniejący kod, nie psując go (OCP)
  • nazywać, dzielić kod na klasy, interfejsy, abstrakcje i tworzyć zdrowe zależności (ISP, DIP)
  • kodować defensywne i chronić swoje dzieła przed nie-SOLID-nymi osobnikami, ale pozostawić kod otwarty na rozbudowę (OCP).
O autorze

O autorze

Michał Cisz

Programistycznie od kilkunastu lat związany z ekosystemem PHP/JS/PostgreSQL. Fan czystej do bólu architektury, metodyk agile i programowania ekstremalnego, dbający o przestrzeganie zasad SOLID.

Czysta architektura okiem ekipy migawka.it

Jakie problemy napotkaliśmy na początku? W czym nam pomogła? Kiedy na pewno z niej nie skorzystamy?

Nie przeszkadzaj mi! Jak nie dać się wyrwać z flow

Ile razy ostatnio zostałeś brutalnie oderwany od swojej pracy? Zaszywasz się w biurze i nagle pojawiają się oni – nieoczekiwani goście i ich pytania, które nie niosą ze sobą grozy awarii albo powagi hotfixa. Ale są to pytania, które teraz, dokładnie w tym momencie muszą paść i oderwać cię od bieżącej pracy. Jak sobie z nimi poradzić?

Precyzyjne twoje wiadomości być muszą

Dostajesz wiadomość e-mail i zastanawiasz się, czy to na pewno do ciebie i co właściwie masz zrobić. Wysyłasz pilną informację na komunikatorze i czekasz tydzień na odpowiedź. Dlaczego? Przecież wszystko było jasne. Najwyraźniej jednak nie było. Chwila moment i dowiesz się wszystkiego.

Testowanie wielu pól

Ponoć kiedy kod jest już napisany nie da się stwierdzić, czy był pisany w modelu TDD. Jest jednak kilka śladów zbrodni, które pozostają. Na przykład testy sprawdzające zbyt wiele rzeczy na raz i zakładające, że czytelnik posiada wiedzę tajemną.

Te bezsensowne interakcje przerywające pracę

Ile razy byłeś poirytowanie faktem, że musisz w pięciu mailach wyjaśniać o co chodzi? Ile razy miałeś wrażenie, że banalne tematy ciągną się w nieskończoność, a ty czułeś się jak niezastąpiona jednostka centralna, bez której nic w zespole się nie zadzieje? Oczywiście wszystko kosztem twojej efektywności i nieustannego przerywania własnej pracy. Posłuchaj pewnej historii i naucz się prostej techniki, która wyeliminuje zbędne interakcje.

Praca zdalna – komunikacja na czacie

Jak przenieść codzienne rozmowy z biura do świata online? Co z bezcennymi dyskusjami w kuchni? Najczęściej wrzucamy całą firmę na jakiś komunikator i liczymy, że zadzieje się magia. Nic bardziej mylnego i dziś opowiem ci jak sobie z tym poradzić.

Funkcja trackback/Funkcja pingback

  1. Zasady SOLID w czystej architekturze - migawka.it - […] Klik! […]
  2. Community Edition SOLID w służbie czystej architektury - migawka.it - […] Kolejna część: SOLID premium. Klik! […]
  3. Enterprise SOLID dla architekta czystych kodów – łączenie - migawka.it - […] zastosował tu odwrócenie zależności (DIP). Komponent Subscription definiuje abstrakcje opisujące, w jaki sposób naliczane są bicia […]
  4. Enterprise SOLID dla architekta czystych kodów – grupowanie - migawka.it - […] Premium SOLID dla praktykujących czystą architekturę […]

Prześlij komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

Pozostań w kontakcie