Zaawansowana część cyklu o zasadach SOLID. To nie będą już pojedyncze klasy. To prawdziwa architektura z pełnią mocy i odpowiedzialności. Poznasz świat komponentów. Nauczysz się jak, zgodnie z zasadami czystej architektury, organizować je i składać z właściwych klas.
Zakładając, że dobrze wykonasz pracę i zastosujesz się do zasad pojedynczej odpowiedzialności (SRP) i segregacji interfejsu (ISP), uzyskasz optymalny zestaw klas. Będą one hermetycznie zamykały swój codebase, jednocześnie pozostając otwarte na rozbudowę (OCP). Dzięki zasadzie podstawień (LSP) i odwróconych zależności (DIP) bez problemu połączysz je ze sobą.
Czujesz, że coś cię ominęło i nie znasz powyższych zasad? Przeczytaj koniecznie:
- Community Edition SOLID w służbie czystej architektury
- Premium SOLID dla praktykujących czystą architekturę
Czy te same zasady SOLID możesz zastosować do większych jednostek – komponentów? Oczywiście że tak, ale nie wprost. Pora na zaczerpnięcie odrobiny wiedzy u źródła.
Teoria SOLID-nych komponentów
Robert C. Martin bazując na SOLID wprowadził 6 zasad odnoszących się do komponentów. Obiecuję, że to jedyny tak czysto teoretyczny kawałek tego artykułu 🤓.
Teoria grupowania klas w komponentach
Trzy pierwsze zasady dotyczą spójności komponentów, zasad grupowania i organizacji klas w tych komponentach.
- Reuse/Release Equivalence Principle (REP) – Podstawą ponownego użycia komponentu jest jego numer wydania. Ziarnistość ponownego wykorzystania i wersji musi współgrać.
- Common Closure Principle (CCP) – W ramach komponentu zgromadź te klasy, które zmieniają się z tego samego powodu i w tym samym czasie.
- Common Reuse Principle (CRP) – Nie zmuszaj użytkowników komponentu do zależności od rzeczy, których nie potrzebują.
Teoria łączenia komponentów
Ostatnia trójka to recepturki jak łączyć i uzależniać od siebie komponenty.
- Acyclic Dependencies Principle (ADP) – Nie dopuść do powstawania grafów cyklicznych w diagramie zależności komponentów.
- Stable Dependencies Principle (SDP) – Zależność kieruj w stronę elementu stabilnego.
- Stable Abstraction Principle (SAP) – Relacja pomiędzy stabilnością a abstrakcyjnością. Komponent powinien być tak abstrakcyjny, jak jest stabilny.
I to tyle definicji. Wyczuwasz nosem mało przydatną teorię i niewiele ci to mówi? Nie przejmuj się. Dokładnie tyle wyniosłem z pierwszej lektury Czystej Architektury R.C. Martina. Potraktuj tę część jako słowniczek, do którego będziesz mógł wrócić w przyszłości.
Tymczasem lecimy dalej z REP, CCP, CRP i… stop! A czym tak właściwie jest mityczny komponent?
Poszukiwanie definicji komponentu
Pierwszy strzał do Google dla frazy “komponent informatyka”
Komponent – niezależnie wytworzony, skompilowany (z ukrytymi szczegółami implementacyjnymi) moduł programowy, udostępniający swą funkcjonalność za pomocą jednoznacznie zdefiniowanego interfejsu, zdolny do współdziałania z większą całością (systemem) oraz innymi komponentami.
Zródło: https://pl.wikipedia.org/wiki/Komponent_(informatyka)
Kompilowana klasyka i skomplikowana skryptówka
W świecie języków kompilowanych, DLL–ek, JAR–ów odpowiedź na pytanie czym jest komponent wydaje się być prosta. To niezależna jednostka wdrożeniowa. Pakujesz kod do takiej jednostki, kompilujesz i dostarczasz bez dotykania pozostałych bibliotek aplikacji.
PHP, Python, JavaScript – języki skryptowe są już wyzwaniem. Czy komponent to po prostu katalog? Może odgałęzienie przestrzeni nazw na N–tym poziomie? Brakuje jednoznaczności.
Dokumentacja frameworka Symfony (PHP) tak definiuje komponent:
Symfony Components are a set of decoupled and reusable PHP libraries. (…) You can use any of these components in your own applications independently from the Symfony Framework.
Idąc tym tropem natrafimy na pojęcie pakietu. Czyli dotrzemy do PHP–owego composera i jego odpowiedników w Python (pip) i JavaScript (npm). Całkiem nieźle 😎, ale to za mało.
Kod własny aplikacji zazwyczaj trafia w jednym kawałku do repo. Niepodzielony, bez wyraźnie oznaczonych jednostek wdrożeniowych. To z kolei negatywnie wpływa na efektywność pracy, zwłaszcza gdy kod jest na tyle duży, że musi być współdzielony przez kilka zespołów.
Jak zatem powinny wyglądać komponenty własne aplikacji?
Komponenty w praktyce
W poprzednim artykule (Premium SOLID dla praktykujących czystą architekturę), pojawił się przypadek startupu Mozartify, który został zaimplementowany przez zespół Janka.
Janek Koderek to doświadczony programista i świeżo upieczony Tech Lead. Naczytał się dużo o czystym kodzie i popełnił go wiele w praktyce. Z zamiłowania jest architektem oprogramowania i fanem metodyk zwinnych.
W dalszej części tego artykułu zobaczysz, jak Janek poradził sobie z czystą architekturą i wydzieleniem core’owego komponentu subskrypcji w aplikacji Mozartify. A zrobił to zgodnie z trzema pierwszymi zasadami czystej architektury komponentów: REP, CCP, CRP.
Komponent Mozartify\Subscription
Popatrz na jedną z pierwszych wersji komponentu subskrypcji. Jest to idealny przykład samoopisującego się kodu.
Komponent mówi:
Jestem modułem subskrypcji. Tu (Domain) jest opis moich możliwości – domena. Posiadam persystencję(FileStorage) i potrafię komunikować się z systemem marketing automation (Pardot).
Janek przygotował dobrą strukturę plików i katalogów. Ale wiedział, że aby skutecznie wdrożyć czystą architekturę potrzeba czegoś jeszcze. Systemowego podejścia w definiowaniu zależności.
Reuse/Release Equivalence Principle (REP)
Wersjonuj komponenty, deklaruj kontakty i je spełniaj. Czy uzależniłbyś się od komponentu, który jest dostarczany jako paczka ZIP bez metadanych, określających jego wersję i zależności od innych pakietów? Pewnie nie.
Managery pakietów
Dlatego tak chętnie korzystasz z managerów pakietów (composer, pip, npm). Również z tego powodu w komponencie poczynionym przez zespół Janka, pojawił się plik composer.json.
Dzięki temu mógł precyzyjnie związać elementy aplikacji. Nie musi się przejmować tym, że update wciągnie coś, co wystrzeli apkę na Marsa…. to nie projekt dla NASA 🚀.
Na przykład komponent Mozartify\Web zależy od dwóch core’owych pakietów: Subscription i Player. Widać to jak na dłoni.
Komponent Mozartify\Web to framework i warstwa kontrolerów odpowiedzialny za warstwę interakcji z userem: Symfony, Laravel w PHP, pythonowy Flask lub Django albo js-owa pochodna Expressa.
Wersjonowanie semantyczne
Ważne, że zespół pracujący nad Mozartify\Web wie, kiedy musi wykonać znaczną pracę. Dokładnie wtedy, gdy któryś z pakietów core’owych podbije pierwszą cyferkę, czyli zmieni publiczny kontrakt. Wiemy to, dzięki konwencji SemVer.
No tak. Ale mówimy o językach skryptowych. W nich nie ma jarów i innych dll-owych czarów. Wystarczy zmienić plik i poczekać na odświeżenie pamięci podręcznej serwera, albo wykonać reload serwera http.
Wartość podziału na komponenty
Czy w takim razie jest sens dzielić kod na komponenty? Przecież masz testy, ciągłą integrację. Wszystko na jednej gałęzi w repozytorium… I ciężko zarządzalny monolit za jeden kodo–rok.
Grupując klasy w komponenty, już na samym początku projektu, chronisz się przed monolitem. Jednak zanim usiądziesz do dzielenia, przeczytaj artykuł do końca. Musisz uwzględnić jeszcze dwie zasady konstruowania pakietów: wspólnego domknięcia i ponownego użycia.
Drugą istotną kwestią jest topologia zespołów. Codebase projektu rośnie, zespół się skaluje. Część ekipy jest na miejscu w biurze, inna na drugim krańcu kabla ethernetowego i jeszcze z przesunięciem strefy czasowej 😈. Współpraca nad monolitycznym workiem ficzerów stanie się niemożliwa. Ale mając komponenty, zwiększasz szanse, że git merge będzie przyjemnością.
Równorzędność ponownego wykorzystania i wydania
Kilka faktów dotyczących komponentów:
- Im większe komponenty, tym wydanie jest łatwiejsze w przygotowaniu.
- Ale im większe komponenty, tym trudniejsze jest zapewnienie ponownego wykorzystania.
- Z drugiej strony im mniejsze komponenty, tym łatwiejsze jest zapewnienie ponownego wykorzystania.
- Niemniej jednak, im mniejsze komponenty, tym wydanie jest trudniejsze w przygotowaniu.
Niemożliwe jest, aby w 100% spełnić te przeciwstawne wymagania. Ważne jest zachowanie równowagi między releasem a reużyciem. Co nie oznacza, że pożądanym stanem jest spełnienie wymagań pół na pół.
W dalszej części poznasz dwie zasady, dwa różne punkty widzenia, które pozwolą ci na dokonanie właściwego wyboru.
Common Closure Principle (CCP)
Pamiętasz zasadę pojedynczej odpowiedzialności (SRP) w SOLID? Zasada wspólnego domknięcia jest czymś analogicznym. Chodzi o to, abyś w ramach komponentu zgrupował klasy, które zmieniają się z tego samego powodu.
Zachłannie grupuj
Przyjrzyj się komponentowi Subscription. Składa się z dwóch klas opisujących domenę (Domain, DomainException), klasy odpowiedzialnej za persystencję (FileStorage) i adaptera do systemu e-commerce (Pardot). Z jakich powodów będą się zmieniały klasy w tym komponencie? Z dokładnie jednego powodu. Modyfikacja kodu będzie efektem zmian w zasadach subskrypcji usługi Mozartify.
Komponent to jednostka wdrożeniowa. Pomyśl o nim jak o mikroserwisie, którym opiekuje się pojedynczy zespół developerski. Niech taki komponent ma wszystkie klasy niezbędne do działania.
Nie dziel za wcześnie
Janek wiedział, że nietrafionym pomysłem będzie zbyt wczesny podział aplikacji na wiele komponentów. Chciał uniknąć tych niepotrzebnych, które później są jak kula u nogi. Zbytnie rozdrobnienie skutkuje tym, że wprowadzenie nowej funkcji oznacza zmianę kilku takich jednostek. Pojedynczy komponent przestaje być wtedy niezależnym blokiem wdrożeniowym.
Scalenie komponentów oczywiście jest możliwe, ale to żmudny i błędogenny proces 🐞. Zmiana przestrzeni nazw, testy, integracja, czasem konieczność komunikacji z innymi zespołami.
Identyfikuj czynności
Zauważyłeś, że domena komponentu subskrypcji to jedna klasa?
Janek wiedział, że naszym naturalnym odruchem jest próba przypisywania nazw – rzeczowników. Czyli usilne poszukiwanie encji.
Zamiast tego zidentyfikował i zgrupował zachowania – czasowniki. Operacje, za które komponent będzie odpowiedzialny. Tak powstała jedna klasa (fasada operacji domenowych), zgodna z zasadą SRP. Klasa ma jeden powód do zmiany, którym jest modyfikacja reguł biznesowych procesu subskrypcji.
W kolejnej iteracji Janek zauważył pewną nadmiarową odpowiedzialność komponentu i dokonał niezbędnej korekty.
Common Reuse Principle (CRP)
Zbędną odpowiedzialnością Mozartify\Subscription była metoda przygotowująca ofertę dla istniejącego i nowego subskrybenta (prepareCommercialOffer).
Do wszystkiego czyli do niczego
Od czego może zależeć oferta? Od cennika i zasad rabatowania w dziale handlowym, od programu lojalnościowego i od innych dziwnych kompilacji pomysłów działu marketingu i e-commerce. To wszystko będzie miało wpływ na komponent Mozartify\Subscribe. Mało stabilna perspektywa.
Janek nie chciał, aby komponent subskrypcji stał się nieprzydatny. Tak by się stało, jeśli pozostałby zależny od niestabilności, związanych z programami lojalnościowymi. Kto chciałby używać tak dynamicznie zmieniającego się komponentu?
Segregacja interfejsów
Dlatego również w przypadku komponentów niezbędna jest segregacja interfejsów, czyli takie wysokopoziomowe ISP.
Janek był zadowolony. Od teraz jeśli będzie miał potrzebę uzależnić jakiś komponent od Mozartify\Subscibe, to zrobi to z chęcią. Nie zwiąże się ze zbędnymi zależnościami. One są już gdzie indziej – w Mozartify\Marketplace.
Nie przeginaj z podziałem
Teoretycznie im mniejsze komponenty tym większe szanse łatwego ponownego użycia. Pozornie. Wyobraź sobie sytuację, w której wprowadzenie zmiany wymaga podbicia wersji pięciu komponentów składowych. To już generuje narzut.
Dołóż do tego zabawny fakt, że trzy komponenty są pod opieką twojego zespołu. Ale dwa kolejne trafiły do innych zespołów. Niezbędna będzie skuteczna komunikacja. O wciągnięciu zmiany wszędzie “na raz” możesz zapomnieć. Aha i ten komponent w twoim zespole, był przez kilka lat utrzymywany przez jednego developera, który trzy miesiące temu złożył wypowiedzenie i już nie pracuje.
Taka całkiem realna kompilacja przypadków techniczno–społecznych. A wiadomo, że jak coś może pójść nie tak, to…
Czyli miej, w przysłowiowym tyle głowy, konieczność zachowania równorzędności ponownego wykorzystania oraz wydania.
Podsumowanie
Po lekturze artykułu, możesz śmiało odhaczyć na swojej todo–liście umiejętność grupowania klas w komponenty. Zostało coś jeszcze do omówienia.
Na poziomie klas SOLID zwykle jest jednoznaczny. Klasa nie może być częściowo abstrakcyjna, ani częściowo stabilna. Zależności, stabilności, abstrakcje i relacje. Płynnie zmierzamy do zasad łączenia komponentów, o których w kolejnym odcinku.
Funkcja trackback/Funkcja pingback