Boli dopiero po czasie. Architektura systemu to nie galeria sztuki nowoczesnej, gdzie stawiasz dziwną rzeźbę z mikroserwisów i liczysz na oklaski; to raczej polisa OC dla kodu. Zaczyna mieć sens dokładnie w momencie, gdy klient rzuca radosne „dodajmy małą funkcję”, a ta drobnostka dotyka siedmiu modułów, trzech baz danych i jednego biednego if-a, który miał żyć wiecznie, a teraz pęka pod presją. Koniec złudzeń. W e-commerce na 120 tysięcy linii kodu, gdzie 6 deweloperów rzeźbiło bez architekta, dodanie prostego rabatu zajmowało po dwóch latach aż 11 dni. Większość tego czasu to debugowanie efektów ubocznych. Kiedy brak separacji odpowiedzialności, wtedy wszystko wie o wszystkim — kontroler zna strukturę tabeli, a walidator wysyła maile. Chaos, tylko że w kodzie.
ISO/IEC 25010 wygląda poważnie. Ma grube PDF-y i tabelki z „maintainability”, ale spróbuj to wdrożyć w sprincie numer 7, gdy deadline wisi nad zespołem jak topór. Teoria robi się miękka. Standard mówi: „system powinien być łatwy w utrzymaniu”, ale nikt nie podaje, czy mierzymy to liczbą klas, czy czasem na pull requesta. Tu pojawia się wątpliwość. Definicja musi pasować i do banku, i do pralki, więc nie pasuje do niczego idealnie. Nawet model 4+1 Philippe’a Kruchtena — logiczny, procesowy, wdrożeniowy, implementacyjny i scenariusze — w małym startupie bywa armatą na muchę. Junior patrzy na 140 stron dokumentacji i czuje, że to plan zdjęciowy, a nie opis API. Często lepiej sprawdza się model C4 Simona Browna: Context, Container, Component, Code. Od ogółu do szczegółu, bez zbędnej filozofii i bez rysowania każdej prywatnej metody w UML, jakby IntelliJ przestał istnieć.
Juniorzy często chcą zbawić świat nowym frameworkiem z GitHuba, który ma 300 gwiazdek i jest „lekki jak piórko”. Pamiętam typa — wpadł na daily z oczami jak po trzech Red Bullach, przepisał moduł płatności i tydzień później produkcja mieliła CPU jak betoniarka. Brak cache’owania i synchroniczne requesty zabiły system. Nie w każdym przypadku winne jest narzędzie, częściej zawodzi brak nudnych rozwiązań typu memoizacja czy kolejka asynchroniczna. Jeśli system ma żyć dłużej niż twój obecny laptop, planuj tak, jakbyś to ty miał go utrzymywać za trzy lata. Bo możliwe, że będziesz. Najgorsze refaktoryzacje robi się pod presją sprzedaży, kiedy nikt nie oddziela odpowiedzialności, bo „termin”. Potem architektura przypomina patchwork zszyty z ticketów z Jiry.
W podręcznikach zobaczysz wykresy czyste jak łza, a w repozytorium — kontroler wywołujący DAO przez statyczny helper z 2009 roku. Legacy kłamie bez mrugnięcia okiem. Dokumentacja twierdzi, że to architektura heksagonalna, a kod odpowiada: „singleton i modlitwa”. Często prawda mieszka w bazie danych, w polach typu isLegacyMode albo clientTier3OverrideFlag, które rządzą logiką od czasu, gdy klient enterprise odszedł dwa lata temu. Jeśli widzisz klasę Utils, to zazwyczaj nie jest żaden util, tylko magazyn wstydu na 1200 linii. Nie poprawiaj każdego zdania do poziomu eseju, po prostu buduj cienki szkielet i pozwól mu się deformować pod realnym obciążeniem. Kod, który przechodzi testy, daje więcej informacji niż najpiękniejszy diagram z 80 prostokątami, którego i tak nikt nie zaktualizuje po pierwszym merge requeście.
Dobra, zejdźmy na ziemię, bo teoretyzowanie o czystym kodzie przy kawie to sport dla ludzi, którzy nie mają ticketów na „już”. Wyobraź sobie, że piszesz prosty system zamówień. Masz encję Order. Wrzucasz tam wszystko: ID klienta, listę produktów, status płatności, logikę obliczania podatku VAT, a na koniec jeszcze metodę do generowania PDF-a z fakturą. Działa? No pewnie, że działa. Przez pierwsze dwa tygodnie jesteś bohaterem. Potem przychodzi zmiana: VAT w Niemczech jest inny niż w Polsce, a faktury mają być wysyłane przez zewnętrzny system API, a nie generowane lokalnie. I nagle twój Order.java ma 800 linii, a każda próba zmiany podatku wywala wysyłkę maili. Gratulacje, właśnie zbudowałeś Big Ball of Mud.
Zastosujmy tu inwersję zależności, zanim zginiesz marnie. Zamiast Order prosić o PDF-a, stwórz interfejs InvoiceGenerator. Niech Order nawet nie wie, czy to generuje biblioteka iText, czy hinduski stażysta przepisujący dane do Notatnika. To jest ta słynna abstrakcja, o której juniorzy zapominają, gdy widzą nową wersję Springa. Logikę biznesową — te wszystkie „jeśli klient z Gruzji i kupił wino, to daj rabat 5%” — zamknij w osobnej usłudze. Niech kontroler tylko odbiera request i przekazuje go dalej. Kontroler to recepcjonista, a nie kucharz. On ma tylko sprawdzić, czy gość ma rezerwację (walidacja DTO), a nie smażyć steki.
Pojawia się wątpliwość: czy warto tracić czas na te wszystkie interfejsy w małym projekcie? Nie zawsze. Jeśli piszesz skrypt do migracji danych, który odpalisz raz i skasujesz, to olej architekturę. Wal to w jednej metodzie main. Ale jeśli to ma być rdzeń biznesu, to oszczędność na architekturze to po prostu branie kredytu w chwilówce na 5000%. Oddasz z odsetkami w postaci nadgodzin w weekendy. Widziałem projekty, gdzie koszt dodania nowej kolumny do tabeli był tak duży, że firma wolała zbankrutować. Serio. Techniczny dług to nie jest metafora literacka, to realne pieniądze uciekające z konta klienta przez dziurawy kod.
Weźmy teraz na warsztat mikroserwisy, bo to słowo działa na juniorów jak magnes. „Zróbmy mikroserwisy, bo Netflix tak robi”. Netflix ma tysiące inżynierów i problemy, o których ci się nie śniło. Ty masz trzyosobowy zespół i problem z poprawnym skonfigurowaniem Dockera. Zanim rozbijesz monolit na 15 kawałków, upewnij się, że potrafisz zarządzać transakcjami rozproszonymi. Bo co zrobisz, gdy serwis płatności powie „OK”, a serwis magazynu wywali błąd 500? Masz wtedy pieniądze klienta, ale nie masz towaru, a system myśli, że wszystko jest w porządku. Architektura to wybór mniejszego zła. Czasem lepiej mieć jeden, dobrze uporządkowany monolit (modułowy monolit to nie jest obelga!), niż „rozproszone spaghetti”, gdzie debugowanie jednego requesta wymaga przeszukania logów w pięciu różnych kontenerach.
Dygresja o nazewnictwie: przestań nazywać wszystko Manager, Processor albo Helper. Te słowa nic nie znaczą. OrderManager to śmietnik, do którego wrzucisz wszystko, od walidacji po zapis do bazy. Nazwij to OrderPriceCalculator, OrderRepository albo OrderValidator. Konkret. Im bardziej precyzyjna nazwa, tym trudniej dopisać tam kod, który tam nie pasuje. To taki psychologiczny trik na samego siebie. Jeśli klasa nazywa się EmailSender, to raczej nie dopiszesz tam logiki przeliczania kursu walut, prawda? A w GlobalService dopisałbyś to bez mrugnięcia okiem.
Na koniec spójrzmy na testy. Architektura, której nie da się przetestować jednostkowo, to po prostu błąd w sztuce. Jeśli twoja logika biznesowa jest tak mocno sklejona z bazą danych, że musisz stawiać całego PostgreSQL-a, żeby sprawdzić prosty warunek if, to przegrałeś życie. Dobra architektura pozwala ci testować zasady gry w izolacji. Infrastruktura — baza, API, system plików — powinna być tylko wymiennym detalem. Dzisiaj masz SQL-a, jutro klient wymyśli NoSQL, a pojutrze wszystko będzie w chmurze AWS S3. Brzmi nierealnie? Przeżyłem takie migracje. Te, które miały warstwy, trwały miesiąc. Te „sklejone” pisaliśmy od zera.
Pamiętaj, juniorze: Twoim zadaniem nie jest pisanie kodu. Twoim zadaniem jest dostarczanie działających rozwiązań, które da się zmienić bez płaczu. Kod to tylko koszt. Im mniej go napiszesz, by osiągnąć cel, tym lepszym jesteś architektem. Więc zamiast rzucać się na najnowsze wzorce z blogów technologicznych, zacznij od pytania: „co się stanie, gdy klient zmieni zdanie?”. Jeśli odpowiedź brzmi „będę musiał przepisać pół systemu”, to znaczy, że czas wrócić do rysowania prostokątów. Tylko tym razem z głową.
Autor: programista z 15 letnim doswiadczeniem, expert, redaktor z zacieciem do pisania lekkich i zartobliwych tekstow
Leave a Reply