Masz problemy z prawidłowym podziałem plików w projekcie, tak aby nie było bałaganu?
Chcesz się tego nauczyć lub dostać jakiś wzór grupowania plików?
Możesz zadawać sobie takie pytania jak:
W tym wpisie nakieruje Cię na odpowiedzi ✅💪
Jedziemy!
Najpierw książka, którą radzę Ci zdobyć.
Autor: Michael Keeling
Dlaczego warto ją mieć?
Najważniejsze rozdziały dla Ciebie na ten moment to:
A tutaj sprawdzisz calutki spis treści tej książki.
Warto przerobić tę książkę od początku do końca, ponieważ ma w sobie zadania praktyczne
Ale luzik, możesz ją zostawić na czas gdy będziesz już na etacie Junior Java Developera.
Polecany odcinek podcastu – zacznij słuchać najlepiej od 8 minuty – wcześniej jest trochę zawile 🙂
Projekt systemu finansowego, w którym znajdziesz przykład:
W kontekście architektury aplikacji musisz wiedzieć czym jest moduł.
Na tym etapie musisz wiedzieć czym jest moduł.
Inspiracją do tych zasad jest szkolenie z Jakubem Nabrdalikiem z firmy Bottega (jeszcze jak pracowałem w Allegro), ale też ogólne podejście Jakuba do tworzenia aplikacji z jego wystąpień na YT, bardzo się to u mnie sprawdza w projektach i polecam Jakuba.
6 zasad, które pomogą Ci stworzyć moduł:
Przykłady procesów to:
Często każdy z tych modułów to po prostu pakiet w Javie.
W takim pakiecie zwykle robimy trzy lub więcej podpakietów, ale to nie jest reguła:
Na poniższym obrazku mógłbym w każdym module tworzyć takie pod pakiety application, infrastructure i domain, ale tak naprawdę to jest kwestia umowna i wolę jak klasy domenowe i aplikacji są płasko na tym samym poziomie, bo widzę co dany moduł robi i nie muszę szukać w pod pakietach.
Infrastruktura jest wyjątkiem – ma swój pod pakiet oraz pakiet dto, w którym trzymam wszystkie klasy publiczne, które służą tylko za reprezentacje danych systemu, są one zwracane przez Controllery.
Kod na githubie.
Dzięki takiemu podziałowi na moduły zmniejsza się coś takiego jak cognitve load.
Przeładowanie kognitywne, to w skrócie znaczy, że im mniejszy ładunek w głowie, tym łatwiej skupić się na implementacji jednego konkretnego modułu nie myśląc w tym czasie o innych modułach.
Do takich czterech modułów może usiąść teraz czterech różnych programistów.
Jednakże te moduły finalnie mają ze sobą współpracować, dlatego, muszą oni na początku ustalić jak będzie wyglądać API każdego modułu (będą to metody publiczne w fasadach).
Wtedy mogą zacząć pracować równolegle nad każdym z modułów.
Unikamy sytuacji, w której programista implementujący moduł emailsender musiałby czekać aż prace nad modułem pdfgenerator, od którego zależy, się skończą.
Po kilku miesiącach każdy z tych programistów będzie ekspertem od swojego procesu i będzie miał w nosie inne moduły – SIC!. (dzięki temu, że mógł się skupić na swojej robocie)
Jest jeszcze jedna kwestia.
Co na poziomie wszystkich modułów robi pakiet infrastructure i czym różni się od pakietu infrastructure, który jest wewnątrz modułów?
W tym module trzyma się rzeczy niezwiązane z domeną aplikacji. Są tam wszystkie implementacje potrzebne do funkcjonowania aplikacji.
Na przykład eventy, security, klienty http do innych serwisów, metryki, kolejki (są to rzeczy, które są niezbędne do działania aplikacji, ale nie są związane z żadnym konkretnym modułem). Zaraz zobaczysz przykłady tego co może być w infrastructure.
A infrastructure wewnątrz modułów przechowuje implementację infrastruktury dotyczącej tylko tego modułu, czyli na przykład:
Zaraz zobaczysz przykłady tego co może być w infrastructure.
Zobacz jak na poniższym schemacie może wyglądać moduł emailsender.
Jeśli logika jest złożona, to powinna zostać wydzielana do osobnych klas, a nie być trzymana w klasie EmailFacade (Fasada powinna być cienka).
W tym przypadku Fasada korzysta z klas EmailSender i EmailRetriever.
Dzięki temu kod klasy EmailFacade sprowadza się do wykorzystania tych klas.
A do tworzenia obiektów wykorzystujesz klasę EmailConfiguration.
Jeśli byśmy podpięli Springa pod to, to wtedy dodajemy andotacje @Configuration i @Bean. Dzięki czemu inne moduły mogą wstrzykiwać tę fasadę u siebie (więcej info tutaj).
Do przeczytania: Czym jest Horizontal i Vertical Layer w programowaniu
(umożliwia dostęp do modułu z zewnątrz)
Klasy Email, EmailRepository, EmailConfiguration i kolejne klasy dotyczące wysyłania maili.
To jest domena aplikacja czyli zachcianki i funkcjonalności przychodzące z biznesu. (kod główny modułu)
(dane aplikacji)
WAŻNE: Nie rób tak, że robisz pakiet WOREK na przykład:
To jest podejście horyzontalne i nie jest to często spotykana praktyka, można nawet powiedzieć, że jest to antypattern. Rób pakiety, które mają wertykalne (pionowe) warstwy, a nie horyzontalne 🙂
Single Responsiblity Principle (literka S z SOLID’a) na poziomie modułu powinno zostać zachowane.
Moduł odpowiada tylko za jeden proces i powinien zmieniać się tylko wtedy gdy zmiany dotyczą tego konkretnego procesu (np. tylko wysyłki wpisu lub tylko generacji pdfa).
Uwaga. To co zaraz przeczytasz o danych modułu może być nieintuicyjne.
Zdaję sobie z tego sprawę, dlatego możesz wrócić jeszcze raz do poniższej sekcji po przeczytaniu całego wpisu.
Moduł ma swoją własną bazę danych, do której żaden inny moduł nie powinien mieć dostępu!
Każdy moduł powinien mieć swoją tabelkę w bazie albo nawet osobną bazę danych. I nikt nie może nic w niej zmienić! Tylko ten jeden moduł może manipulować swoimi danymi. To jest właśnie enkapsulacja kodu i danych w module.
Mowa tutaj oczywiście o jednej aplikacji z wieloma modułami, a nie o mikroserwisach. W mikroserwisach naturalnym jest że każdy mikroserwis ma swoją własną bazę danych.
Nie opieraj swojej aplikacji wielomodułowej na jednej bazie danych*!
*baza danych jest tutaj tylko uproszczeniem – to wcale nie musi być jedna baza danych na jednej maszynie.
Izolacją może być to, że każdy moduł ma swoją tabelkę w bazie danych. Ale chodzi o to, żeby żaden inny moduł nie miał dostępu do tych danych bezpośrednio. Może tylko komunikować się przez API modułu!
Ale co jeśli każdy moduł będzie miał swoją “bazę danych”?
Co ze synchronizowaniem danych i duplikacjami?
Czy to nie zabije całego systemu, gdy kilka modułów potrzebuje przechowywać dokładnie te same dane?
To jest bardzo mylne podejście.
Na tym obrazku skup się na module 1 data i module 3 data.
Każdy z modułów ma swoje dane i nikomu nie chce ich bezpośrednio udostępniać (robi to przez metody swojego API).
Przecież ten obrazek reprezentuje wciąż jedną aplikację! To może być ta sama baza danych ale inne tabelki/kolekcje w bazie.
Jeśli cofniemy się o 30 lat, to wtedy trzymanie wszystkiego w jednej bazie i pozwolenie całej aplikacji oraz wszystkim jej modułom na dowolne operacje na wszystkich danych we wspólnej bazie miało sens.
To dlatego na studiach uczą normalizować dane (zazwyczaj to, co jest na studiach jest minimum 10 lat do tyłu do tego co jest w komercyjnych projektach).
Są to stare praktyki, które pozwalają m.in na optymalizacje wykorzystania pamięci bazy danych.
Nie było wtedy aż tyle pamięci, by pozwolić na nadmiarowe duplikacje danych. Pamięć była po prostu droga i każde optymalizacje były mile widziane.
Ale na te czasy jest to antywzorzec.
Firmy takie jak Oracle czy IBM promują ten antywzorzec.
A wiesz, dlaczego tak jest?
Bo one sprzedają soft bazodanowy i wsparcie do ich baz 😛
Dostęp do modułu jest możliwy jedynie poprzez API, które wystawia na użytek innych modułów. A nie przez zaglądnięcie do jego bebechów.
Wszystkie klasy, interfejsy w module, oprócz API, powinno być ukryte i być prywatne (słówko private w Javie lub nie mieć żadnego słówka (wtedy jest dostępne tylko w obrębie pakietu Javowego – a taki moduł to właśnie może być pakiet)).
Komunikacja między modułami odbywa się tylko za pomocą ich klas API (często ta klasa to Fasada z publicznymi metodami).
Pojedynczy moduł sam w sobie może robić bardzo mało i nie być samowystarczalny. Na przykład w module od zamówień potrzebna jest informacja o tym czy użytkownik istnieje w bazie (moduł uwierzytelniania).
Często moduły komunikują się między sobą (mogą to robić tylko poprzez swoje API)
Nie zawsze da się w łatwy sposób wyciągnąć moduł do mikroserwisu.
Latency
Dochodzi opóźnienie (latency) spowodowane połączeniem po sieci.
Porównaj sobie dwie aplikacje mikroserwisowe (dwa osobne serwisy komunikujące się bezpośrednio po HTTP lub za pomocą eventów), z jednym monolitem, która zawiera dwa moduły (czyli dwa moduły w jednej aplikacji).
Co będzie miało mniejsze opóźnienia z perspektywy użytkownika korzystającego z tych aplikacji? Oczywiście, że monolit.
Tracisz transakcje
W aplikacji monolitycznej jesteś w stanie utrzymywać transakcje bazodanowe między modułami, czyli dbać spójność danych w bazie (jeśli jedna operacja zawiedzie w transakcji to cała transakcja jest rollbackowana). Więcej o transakcjach w przyszłym wpisie o bazach danych!
A jeśli wyciągniesz moduł do mikroserwisu to stracisz transakcje!
(nie ma czegoś takiego jak rozproszona transakcja).
W takim wypadku musisz się ratować architekturą opartą o zdarzenia lub innymi wzorcami (wspomnę o tym na końcu wpisu).
Zawsze przy tworzeniu nowego modułu upewnij się, czy spełnia on powyższe zasady.
Masz teraz zestaw narzędzi, żeby się upewnić, czy twój moduł jest dobry.
Obejrzyj koniecznie video w języki polskim o tym jak pakietować i dzielić na moduły: tutaj (gorąco polecam).
Wiemy czym jest moduł więc możemy przejść do architektury aplikacji.
Architektura aplikacji to po prostu organizacja Twojego projektu, czyli:
Na tym etapie nie komplikuj sobie życia i naucz się tych najbardziej popularnych schematów w projektach komercyjnych.
Zacznij od architektury warstwowej, a potem przejdź do heksagonalnej.
Ale jeśli nauczysz się dobrze tylko warstwowej, to z pewnością do twoich prywatnych projektów to wystarczy.
(heksagon wprowadza dodatkową złożoność i niepoprawnie zastosowany może być tylko utrapieniem).
Ready? To lecimy!
Architekturę warstwową aplikacji możemy sprowadzić do prostego podziału aplikacji na techniczne warstwy.
Jego najpopularniejszą odmianą jest model trójwarstwowy.
Zobacz jak wygląda przykład z książki Design IT!
Najlepszym i najbardziej efektywnym co możesz zrobić, żeby poznać tę architekturę, jest skorzystanie z gotowych materiałów w sieci.
Mogę Ci polecić niedrogi warsztat video koszt ok. 50 zł (1h 30 min).
Po nim na pewno raz a dobrze poznasz praktykę i teorię na temat architektury warstwowej.
Bardzo ważną kwestią w kontekście architektury warstwowej i czymś, co musisz poznać jest też mapowanie obiektów.
Skoro warstwa niżej nie może wiedzieć nic o warstwie nad nią, to potrzebujemy stworzyć jakiś wspólny język międzywarstwowy, którym będą się komunikowały.
Jest to świetnie wytłumaczone w tym video.
Polecane Darmowe Materiały (architektura warstwowa):
Z dobrych praktyk korzystania z architektury warstwowej:
źródło DNA
Powinno się stosować taką architekturę wzdłuż wartstw, ale per moduł. Żeby nie doprowadzić do sytuacji, że powiedzmy moduł z kontrolerem REST, który pokazuje liczbę zalogowanych użytkowników w warstwie prezentacji korzysta bezpośrednio z dwóch różnych modułów w warstwie logiki biznesowej np. obliczenie liczby użytkowników online oraz wydrukowanie 1000 kartek świątecznych dla klientów.
Drukowanie kartek to jest totalnie inny moduł i warstwa prezentacji powinna mieć osobny moduł z kontrolerem do akcji na tym module.
Wtedy łatwiej utrzymać taką aplikację w architekturze warstwowej.
Oki to tyle, przejdź do materiałów lub idź dalej…
Warstwy za nami, teraz heksagon!
Architektura heksagonalna ma na celu stworzenie luźno powiązanych komponentów aplikacji, które można łatwo połączyć za pomocą portów i adapterów. To sprawia, że komponenty są wymienialne na każdym poziomie i ułatwia automatyzację testów.
W rzeczywistości nie warto jej stosować do prostych aplikacji.
Najczęściej stosuje się ją w aplikacjach związanych z integracjami różnych systemów.
Na przykład: kilka banków i ich API do płatności, kilka baz danych z różnych powodów mongodb, mysql.
Chodzi o to, by być bardzo elastycznym i w razie problemów mieć możliwość łatwo wymienić jakąś konkretną implementację. (przygotowanie się na zmiany, gdy jeszcze nie jesteśmy pewni). Architektura warstwowa też jest dostosowana do zmian, ale jeśli aplikacja jest prosta to nie musimy odraczać zmian. (last responsible moment)
Podobnie jak dla warstw, polecam Ci niedrogi 2-godzinny warsztat video koszt ok. 60 zł.
Po nim będziesz mieć praktykę i teorię na temat architektury heksagonu
(zrób to po nauce warstw).
Polecane Darmowe Materiały (architektura heksagonalna):
A jak rysować architekturę aplikacji (diagram klas)?
Proste. Ja tego nie robię.
Diagram UML jest uciążliwy i rzadko się z nim spotykam (jedynie na studiach było to ubóstwiane). Taki statyczne diagram np. w notacji UML i tak bardzo szybko się dezaktualizuje. Projekt się rozwija, powstają nowe klasy, moduły i zależności.
Jak już bardzo bym potrzebował diagram klas (architekturę aplikacji), to wygenerowałbym go sobie za pomocą funkcji Intellija w wersji Ultimate.
O wiele bardziej praktycznym podejściem jest wizualizacja systemu (o czym za moment).
I tyle z architektury aplikacji.
Czas poczytać o architekturze systemowej. Lecimy? 🙂
Architektura systemu to architektura całego systemu.
Cały system to połączenie: wielu aplikacji Springowych, zewnętrznych usług innych firm, baz danych, kolejek oraz innych przeróżnych kontenerów.
Na tym etapie polecam Ci tworzyć swoje projekty jako modularne monolity.
Tak naprawdę, póki nie dostaniesz się do pierwszej pracy, to możesz nie potrzebować, pisać w stylu mikroserwisowym.
Mikroserwisy zazwyczaj stosuje się gdy zespołów w firmie jest bardzo dużo i zależy nam na niezależnym rozwoju małych aplikacji.
Dzielisz swoją jedną aplikację (jedno repo), na niezależne (autonomiczne) moduły. Zazwyczaj każdy moduł jest ukryty za API.
Bardzo fajnie się tutaj sprawdza wzorzec Fasada, który ukrywa implementację całego modułu i wystawia ładne API wejściowe. Jest to jedyny punkt wejścia do modułu. Wymusza to, że każdy inny moduł w tej aplikacji musi komunikować się przez fasadę. Jest to świetnie omówione w tym video (1 godzina).
Zauważ, że każdy z tych modułów mógłby komunikować się przez sieć np. API REST tak jak w mikroserwisach. Ale w monolicie, który jest modularny (modularny monolit) nie ma trzeba komunikować się po sieci, bo wszystko siedzi na jednym serwerze.
Polecane Materiały (modularny monolit):
Mikroserwisy są to, tak naprawdę, poszczególne moduły wyciągnięte z modularnego monolitu tylko, że są rozproszone po sieci (mówi się, że jest wiele jednostek wdrożeniowych).
Jest to architektura, w której całość aplikacji tworzą luźno ze sobą połączone, współpracujące serwisy, każdy odpowiadający za fragment funkcji biznesowych całego systemu.
Polecane Materiały (mikroserwisy):
Na zakończenie omawiania architektury systemowej, możesz mieć w głowie wiele pytań (wybacz!), ale jedno z nich byłoby bardzo praktyczne:
Odpowiedzią na to pytanie jest MODEL C4.
Model C4 odpowiada za 4 poziomy: C1, C2, C3, C4.
Na każdym poziomie jest inny stopień szczegółowości systemu.
Zaczynając od C1, czyli spojrzenia i zamodelowania całego systemu z góry, kończąc na C4, czyli diagramie klas w aplikacji (jest to opcjonalny poziom).
Przykładowa implementacja modelu C4 dla systemu bankowego.
Level C1: Diagram CAŁEGO systemu.
Level C2: Diagram technicznych kontenerów np. baza danych, aplikacja webowa (spring).
Level C3: Diagram MODUŁÓW. Pamiętasz moduł z początku tego wpisu? 🙂
Zamiast męczyć się z uciążliwym UML’em, polecam Ci poznać właśnie ten sposób modelowania.
(Istnieje też biblioteka structurizr, która pomaga generować te poziomy na podstawie aktualnego kodu twojej aplikacji! – warto wypróbować).
Polecane Materiały (model C4):
I to by było na tyle z architektury systemowej i aplikacyjnej.
W kontekście architektury warto jeszcze wspomnieć o kilku ważnych pojęciach, czyli…
ADR (Architecture Decision Record) to zapis decyzji architektonicznej.
Zespoły często muszą podejmować decyzje architektoniczne. Dobrą praktyką jest zapisywanie takich decyzji dla przyszłych pokolen, ale tez dla nas samych (łatwo zapomnieć szczegóły nawet po dwóch tygodniach).
Takie decyzje zapisuje się w pliku tekstowym np. google docs.
Taki rekord jest zdarzeniem w historii, dzięki któremu nowe osoby w zespole mogą w łatwy sposób prześledzić rozwój architektury aplikacji od jego powstania do teraz (jest to ustrykturyzowane zapisywanie wyborów i podawanie przyczyn, dlaczego coś wybraliśmy jako zespół).
Takie wybory/decyzje to na przykład:
Polecane Materiały (ADR):
źródło Kamil Grzybek
Na tym obrazku zauważysz wspomniane dzisiaj mikroserwisy i modularny monolit. Jak widzisz wszystko zależy od tego jak bardzo ma być rozproszony system (nr of deployment units – liczba jednostek wdrożeniowych np. 100 serwisów, a nie jeden 1 jak w przypadku wielkiej kuli błota czy modularnego monolitu) i jak bardzo ma być podzielny na moduły (modularity). Więcej na ten temat w tym video.
Event Event Event, wszędzie event o co chodzi?
Event Storming – zdarzenie biznesowe (np. wysłano wpisu) – więcej
Event Sourcing – jakie zdarzenia, nie tylko biznesowe, warto zapamiętać w bazie danych? – więcej
Event Driven Architecture – komunikacja oparta o zdarzenia – więcej i więcej
Do poczytania tutaj.
CRUD (od ang. create, read, update, delete, tłum. utwórz, odczytaj, aktualizuj, usuń) – cztery podstawowe funkcje w aplikacjach korzystających z pamięci trwałej, które umożliwiają zarządzanie nią. ~ wikipedia
Aplikacja typu CRUD może być ogromna, ale jej złożoność jest bardzo niska. Typowy CRUD nie ma żadnej logiki biznesowej. Jest to po prostu zapis i odczyt i tyle. W takich sytuacjach nie ma sensu stosować DDD (o którym zaraz), oraz stosować rozbudowanej architektury aplikacji (np. portów i adapterów).
Domain Driven Design (architektura sterowana domeną) pojawia się gdy logika biznesowa jest bardzo złożona.
Na przykład rezerwacja stolika w restauracji. Musisz przejść pewien proces weryfikacji danych osobowych, a dodatkowo ustawianie gości przy stolikach jest skomplikowane np. zależy od ich profilu.
Masz 100 gwiazdek kliencie?
Super wyrzucimy inną osobę ze stolika, do którego chcesz usiąść.
Teraz wchodzi programista i pyta: Ale co to znaczy gwiazdka? Jak ją zdobyć? Jak zakodzić to w naszej aplikacji?
Programiści mogą popełniać błędy w komunikacji z biznesem.
Jeśli tak zaczyna się wszystko komplikować to wtedy warto zasięgnąć po DDD, który pozwoli ustalić wspólny język między programistami a ludźmi z biznesu (np. Product Managerem).
W dużym skrócie to podejście pozwala na zaprojektowanie i rozwój warstwy domeny oraz kontrolę nad jej złożonością szczególnie w przypadku dużych aplikacji (duże zazwyczaj są złożone, ale nie zawsze, patrz CRUD). Warstwą domeną jest np. rezerwacja stolika lub
Jeśli na tym etapie bardzo chcesz poznać DDD, to polecam Ci zacząć od taktycznego DDD, ponieważ taktyka dotyczy WYKORZYSTANIA DDD w praktyce (w kodzie).
Strategiczne DDD mogłoby Cię przytłoczyć, bo jest mocno abstrakcyjne i z mojego doświadczenia wyglądało to tak, że kod lepiej do mnie przemówił na początku, a potem doczytywałem teorię.
Tak czy siak, jeśli umiesz w DDD (cholernie skomplikowana i czasochłonna dziedzina), a jesteś kandydatem na juniora, to firma, do której się rekrutujesz zaproponuje Ci stanowisko mida 😀
Polecane Materiały (DDD):
Częstą praktyką w projektach jest wprowadzenie automatycznych testów, które sprawdzają, czy ktoś nie naruszył założeń wybranej architektury aplikacji.
Można łatwo napisać testy, a w nich reguły.
Zazwyczaj takie testy piszę się uzgadniając z zespołem konwencje i standardy kodowania.
Na przykład biblioteka ArchUnit, która pozwala pisać testy jednostkowe sprawdzające, czy programista nie naruszył jakiejś z reguł (te reguły piszą programiści – sprawdź polecane materiały).
W indywidualnym projekcie, na Twoim etapie, nie robiłbym tego.
Ewentualnie jako ćwiczenie zastosowałbym typowe reguły:
Polecane Materiały (Testowanie architektury):
źródło: Guru99
W odróżnieniu od typowej architektury warstwowej, którą możesz spotkać np. w książce Design IT!
MVC to 3 niezależne warstwy, ale rozmawiające ze sobą w kółeczku, a nie z góry na dół 🙂
Warto wiedzieć co znaczy ten skrót i sprawdzić jak wygląda implementacja, bo nawet Spring stworzył zależność Spring MVC. Natomiast ja, szczerze mówiąc, nie spotkałem się z pytaniem o MVC na żadnej z moich rozmów rekrutacyjnych na Juniora. Daj znać jeśli Ciebie pytali 🙂
Polecane Materiały (MVC):
Przykład implementacji w czystej Javie – wpis ENG
Pamiętaj!
80% procesu rekrutacyjnego na Junior Java Developera sprowadza się do znajomości:
Reszta procesu rekrutacyjnego (20%) to
(zadbaj o to, by mieć silne podstawy, a dopiero potem leć w drugą kategorię)
Oprócz pakietowania to wiedza z tego wpisu, jest naprawdę fajnym nice-to-have, ale nie must-have. Skup się na 80%, a nie na 20%.
Te wpisy mają Ci pomóc się wyróżnić z osób, które umieją już te 80% i nie dostają odpowiedzi na CV.
Co do architektury.
Jeśli w przyszłości lub teraz myślisz, żeby pójść dalej i uczyć się architektury oprogramowania (w kontekście Javy), to gorąco polecam Ci kurs DNA od devstyle.
Za taką wiedzę warto zapłacić, ale jeśli dążysz do pierwszej pracy w Javie, to na Juniora wystarczą Ci podstawy podane w tym wpisie.
Pozdrawiam!
Bartek Kalka.