Większości programistów sektor ubezpieczeń kojarzy się z technologicznym skansenem i utrzymywaniem przestarzałych monolitów. Nic bardziej mylnego. W świecie, w którym wyliczenie składki opiera się na setkach zmiennych w ułamku sekundy, rzemiosło inżynierskie osiąga najwyższy poziom. Sławomir Meller, Starszy projektant-programista w Comarch, wrozmowie odczarowuje mity wokół Domain-Driven Design (DDD), tłumaczy, dlaczego mądrze zaprojektowany monolit to potężna maszyna, a nie przeżytek oraz wyjaśnia, dlaczego współczesny programista musi być przede wszystkim projektantem systemu, a nie „zwykłym koderem”.
Zanim przejdziemy do szczegółów architektonicznych, załatwmy jedną rzecz. DDD, czyli Domain-Driven Design – w teorii brzmi pięknie, ale w praktyce obrosło wieloma mitami. Jak Ty, jako praktyk, zdefiniowałbyś DDD koledze po fachu, który do tej pory pisał głównie proste aplikacje typu CRUD? Czym to podejście różni się od „zwykłego” programowania?
Gdybym miał to wytłumaczyć koledze, powiedziałbym, że „zwykłe” programowanie typu CRUD skupia się na technikaliach – jak włożyć dane do bazy i jak je z niej wyciągnąć. DDD z kolei wymaga od nas zrozumienia fundamentalnej kwestii: co właściwie robimy i po co. W DDD najpierw musimy dogłębnie zrozumieć świat biznesu i jego ostateczny cel, a dopiero potem opisujemy ten świat za pomocą kodu. Dobrze napisany kod w architekturze DDD stanowi pewną opowieść. Możemy go czytać niemal jak książkę, płynnie rozumiejąc opisywane procesy.
Bardzo lubię tę metaforę o budowaniu samochodu. Wyobraźmy sobie inżyniera, który ma zaprojektować koło. Jeśli skupi się wyłącznie na tym jednym, małym wycinku, kompletnie nie rozumiejąc szerszego obrazu – że na końcu kierowca musi wsiąść i pojechać do przodu – może doprowadzić do absurdalnej sytuacji, w której każde koło będzie obracać się w inną stronę. U nas w firmie mamy nawet takie powiedzenie: programista, który nie rozumie, co i po co robi, a skupia się wyłącznie na izolowanym wycinku technicznym, jest po prostu „zwykłym koderem”, a nie projektantem systemu.
Sławomir, ubezpieczenia to świat gigantycznej i zawiłej logiki biznesowej – taryfy, polisy, aneksy, likwidacja szkód. Dlaczego w tak skomplikowanym środowisku Domain-Driven Design (DDD) to nie tylko „modne hasło”, ale wręcz konieczność?
Systemy ubezpieczeniowe to oprogramowanie o gigantycznej skali i złożoności. W ubezpieczeniach nie mówimy o prostym koszyku w sklepie internetowym, którego działanie każdy z nas rozumie intuicyjnie. To potężna machina, obwarowana mnóstwem regulacji prawnych, a samo wyliczenie składki na polisie to wysoce skomplikowana kwestia, którą zajmują się specjaliści.
Kiedy nad rozwojem tak ogromnego systemu pracuje kilkadziesiąt osób, odpowiednia metodologia to nie jest wybór – to kwestia "być albo nie być" dla utrzymania i rozwoju aplikacji. DDD ratuje nas przed chaosem z dwóch kluczowych powodów. Po pierwsze, sprawia to, że wszyscy w zespole operujemy dokładnie tymi samymi pojęciami i równie dobrze je rozumiemy. To z kolei niesamowicie ułatwia płynne przełożenie wymagań z dokumentacji analitycznej bezpośrednio na kod, który działa pod spodem.
Gdybyśmy zrezygnowali z tego podejścia, po relatywnie krótkim czasie doprowadzilibyśmy do sytuacji, w której my sami przestalibyśmy rozumieć, co właściwie dzieje się w naszym systemie. A proszę mi uwierzyć, że wprowadzenie nowej osoby do zespołu, która miałaby odnaleźć się w takiej plątaninie bez jasno wydzielonych pojęć biznesowych, graniczyłoby z cudem.
W DDD kluczowe jest pojęcie Bounded Contexts (ograniczonych kontekstów) oraz wspólnego języka (Ubiquitous Language). Jak w Waszych projektach chronicie czystość domeny biznesowej przed „zanieczyszczeniem” technicznym i jak dogadujecie ten wspólny język z analitykami?
Ochrona czystości domeny zaczyna się u nas na długo, zanim napiszemy pierwszą linijkę kodu. Fundamentem jest Ubiquitous Language, czyli wspólny słownik pojęć. Ustalamy go z klientem na samym początku współpracy, aby mieć absolutną pewność, że mówiąc o danej rzeczy, wszyscy mamy na myśli dokładnie to samo.
Mając taki słownik, tworzymy w oparciu o niego całą dokumentację – analityczną, projektową i dla użytkownika. Nie ma tu miejsca na domysły. Jeśli chodzi o Bounded Contexts, to nasza organizacja pracy jest ich naturalnym odzwierciedleniem. Zespoły są podzielone domenowo – mamy na przykład zespół specjalizujący się wyłącznie w module finansowym. W ramach tych zespołów mamy swoje specyficzne pojęcia, których rygorystycznie przestrzegamy.
A jak chronimy ten świat przed „zanieczyszczeniami”? Kluczem jest komunikacja. Codziennie spotykamy się w gronie analityków, projektantów i deweloperów na tzw. warmupach. Rozmawiamy o bieżących zadaniach i natychmiast wyłapujemy kwestie wymagające doprecyzowania. Do tego dochodzą planningi i indywidualne konsultacje. Ta ścisła, codzienna współpraca i ciągła rozmowa to nasz główny mechanizm obronny przed tym, by technologia nie przysłoniła nam procesów biznesowych.
Agregaty i encje w systemach ubezpieczeniowych potrafią być ogromne (np. cała polisa ze wszystkimi ryzykami i historią). Jak projektujecie granice agregatów, żeby z jednej strony zachować spójność danych, a z drugiej – nie zabić wydajności systemu?
Projektowanie tak złożonych struktur, jak polisa ubezpieczeniowa, to zdecydowanie nie jest zadanie dla jednej osoby. U nas agregaty i encje są projektowane przez bardzo doświadczonych projektantów we ścisłej współpracy z głównym architektem i liderami technicznymi.
Jeśli chodzi o wyznaczanie granic, robimy to bardzo kategorycznie – wyraźnie wskazujemy, które encje przynależą do konkretnej domeny i który zespół lub obszar jest za nie odpowiedzialny. Każda encja jest rygorystycznie przemyślana pod dwoma kątami. Pierwszy to oczywiście zakres danych, jaki ma zbierać. Ale to ten drugi aspekt jest absolutnie kluczowy: jak ta encja będzie realnie wykorzystywana w działającym systemie.
To właśnie to kryterium użycia pozwala nam utrzymać wydajność w ryzach, zachowując jednocześnie wygodę i łatwość implementacji. Co ważne, my w tych kwestiach nie zgadujemy. Wszystkie trudne decyzje projektowe są na bieżąco konsultowane z gronem ekspertów. Jeżeli mamy jakiekolwiek wątpliwości, czy dany kształt agregatu nie stanie się wąskim gardłem, przygotowujemy POC (Proof of Concept). Zanim wprowadzimy cokolwiek do głównego nurtu, weryfikujemy w boju, czy zaproponowane rozwiązanie faktycznie się sprawdzi pod obciążeniem.
Skoro w DDD kluczowe jest zrozumienie domeny biznesowej ubezpieczeń, to jak w tym świecie odnajduje się Business Systems Analyst (BSA)? Jak blisko z nim współpracujesz i dlaczego silny analityk to dla projektanta systemów najlepszy partner w dyskusji?
W tak złożonej domenie rola analityka jest absolutnie nie do przecenienia, a nasza współpraca jest niezwykle ścisła. Nie ma mowy o pracy w silosach. Widzimy się z całym zespołem codziennie na warmupach, a do tego dochodzą ciągłe, indywidualne konsultacje.
W DDD najważniejsze jest zrozumienie tego, co i po co właściwie robimy. Silny analityk jest dla projektanta najlepszym partnerem, ponieważ z jednej strony perfekcyjnie tłumaczy nam wizję i potrzeby biznesowe, a z drugiej – jest otwarty na nasz feedback. U nas większość rozwiązań jest konsultowana na bardzo wczesnym etapie pod kątem wykonalności i ograniczeń technicznych.
To jest gwarancja dostarczania oprogramowania najwyższej jakości. Taka codzienna, symbiotyczna współpraca pozwala nam drastycznie zminimalizować błędy, które mogłyby pojawić się w gotowym systemie. Największym sukcesem dla duetu analityk-projektant jest moment, w którym wyłapujemy potencjalnie problematyczne architektonicznie rozwiązania już na etapie samej analizy – zanim w ogóle powstanie pierwsza linijka kodu.
Mówimy o zaawansowanej architekturze, ale ktoś musi to potem przenieść na ekran użytkownika. Jak w świat czystej domeny DDD wchodzi Fullstack Developer (Java + Angular)? Jakie wyzwania czekają na niego, gdy musi połączyć skomplikowany, wydajny backend z nowoczesnym frontendem?
Największą zaletą Fullstack Developera jest to, że posiada kompetencje, by zrealizować zadanie od A do Z. Widzi cały przepływ informacji – od zawiłej logiki na backendzie po sam interfejs użytkownika. Jednak w tak złożonym świecie jak ubezpieczenia, Fullstack absolutnie nie działa w próżni i nie musi poruszać się po omacku.
Mamy ten proces bardzo dobrze poukładany. Dla części wizualnej programista otrzymuje makiety w Figmie, przygotowane przez ekspertów UX i grafików. Z drugiej strony ma pełną dokumentację biznesową na Confluence, a z trzeciej – precyzyjną dokumentację techniczną opisującą modele. Wszystko to jest spięte naszymi wewnętrznymi wzorcami projektowymi dla powtarzalnych klas problemów oraz jasnymi wytycznymi od Tech Leadów. To sprawia, że przejście od samego wymagania do jego realizacji jest niezwykle płynne.
Co więcej, kluczem jest u nas komunikacja. Codziennie rozmawiamy, a podczas planningów precyzyjnie określamy zakres zadań. Każda mniej doświadczona osoba ma przestrzeń, by konsultować swoje pomysły z liderami technicznymi, których u nas nie brakuje. I mamy też jedną, żelazną zasadę: żadna linijka kodu nie trafia do głównego repozytorium bez rzetelnego Code Review przeprowadzonego przez doświadczonego eksperta. Nic nie prześlizgnie się bez dodatkowej weryfikacji.
Wysoka wydajność w ubezpieczeniach kojarzy się m.in. z taryfikacją (wyliczaniem składki w ułamku sekundy na bazie setek zmiennych) albo masowym generowaniem dokumentów. Z jakimi największymi wyzwaniami wydajnościowymi mierzysz się jako Starszy projektant?
Największym wyzwaniem dla mnie jako projektanta jest optymalizacja ciężkich, masowych procesów. To zadanie wymaga głębokiej wiedzy o technologiach, z których korzystamy, a absolutnie kluczowa jest tu znajomość mechanik bazodanowych – i to nie tylko ich możliwości, ale przede wszystkim ich fizycznych ograniczeń.
Do architektury podchodzimy bardzo świadomie. Zupełnie innej strategii wymagają od nas procesy opierające się na bazach dokumentowych, a inaczej projektujemy rozwiązania dla klasycznych baz relacyjnych. Kolejnym potężnym wyzwaniem jest taryfikacja. Procesy takie jak wyliczanie składki ubezpieczeniowej muszą być zaprojektowane tak, by wykonywały się błyskawicznie, mimo że pod spodem pracują szalenie skomplikowane formuły matematyczne i algorytmy.
Tego typu wyzwań wydajnościowych mamy w systemie mnóstwo, a do ich rozwiązania nie ma jednej "srebrnej kuli". Wymaga to elastyczności i stosowania zróżnicowanych technik. W takich momentach nieocenione okazują się duże, poparte praktyką doświadczenie naszych inżynierów, biegła znajomość wzorców projektowych oraz wsparcie merytoryczne ze strony liderów technicznych.
Częstym problemem w systemach o dużej współbieżności i skomplikowanej domenie są blokady bazodanowe oraz wąskie gardła. Jakie wzorce architektoniczne (np. CQRS, Event Sourcing, asynchroniczność) pomagają Wam utrzymać system na najwyższych obrotach?
Zawsze powtarzam, że w architekturze nie ma czegoś takiego jak jeden "złoty wzorzec" do wszystkiego. Zdecydowanie unikamy podejścia, w którym, mając w ręku popularny młotek, wszędzie dookoła widzimy tylko gwoździe. Dobieramy narzędzia i wzorce precyzyjnie, adekwatnie do konkretnej klasy problemów i wyzwań.
Stosujemy bardzo zróżnicowane podejścia. Jeśli chodzi o radzenie sobie z dużą współbieżnością i blokadami, to w modułach opartych na bazach dokumentowych najczęściej wykorzystujemy wzorzec blokowania optymistycznego (optimistic locking). Natomiast tam, gdzie silnik opiera się na relacyjnej bazie danych, zazwyczaj sięgamy po blokowanie pesymistyczne (pessimistic locking).
Równie świadomie podchodzimy do komunikacji między modułami, które korzystają z różnych technologii bazodanowych. Do bieżącej komunikacji synchronicznej używamy standardowego REST API. Z kolei tam, gdzie potrzebujemy uwolnić zasoby i przejść na komunikację asynchroniczną, by obsłużyć duży ruch, wkracza Apache Kafka lub niezawodny wzorzec Outbox (skrzynki nadawczej). Taki arsenał pozwala nam utrzymać wydajność w ryzach, niezależnie od scenariusza.
Wysoka wydajność to także odpowiedni dobór narzędzi i optymalizacja na poziomie kodu (np. w Javie). Czy mógłbyś uchylić rąbka tajemnicy, jak testujecie i profilujecie systemy, by mieć pewność, że wytrzymają nagłe piki ruchu (np. w okresach odnowień polis)?
Testowanie wydajnościowe to u nas nieodłączny element procesu twórczego, za który odpowiedzialność bierze sam deweloper. Gdy zachodzi taka potrzeba, schodzimy na bardzo niski poziom i profilujemy aplikację za pomocą narzędzi takich jak JProfiler, by dokładnie sprawdzić, jak zachowuje się nasz kod.
Jednak z praktyki wiemy, że najczęstszym wąskim gardłem w systemach o tej skali nie jest sam kod, lecz baza danych. Dlatego absolutnie kluczową sprawą jest dla nas rzetelna weryfikacja planów zapytań (query execution plans). Zanim wypuścimy cokolwiek do świata, upewniamy się, że to, co wysyłamy do bazy, jest optymalne i nie "zadławi" systemu przy masowym ruchu.
Oczywiście, same testy to nie wszystko. Równie ważnym, o ile nie ważniejszym, aspektem projektowania systemu jest dla nas właściwe przemyślenie kwestii monitoringu. Zbieramy odpowiednie metryki i na bieżąco je analizujemy. Cel jest prosty: mieć na tyle dobrą widoczność tego, co dzieje się w systemie, aby móc proaktywnie reagować na anomalie wydajnościowe, zanim problem w ogóle zdąży dotknąć klienta na środowisku produkcyjnym.
Jesteś Starszym projektantem-programistą. Twoja rola łączy rysowanie "szerszego obrazu" architektury z codziennym pisaniem kodu. Jakie cechy umysłu musi mieć programista, żeby odnaleźć się w świecie DDD i systemów high-performance? Czy sam suchy skill w danej technologii wystarczy?
Znajomość samej Javy czy konkretnego frameworka to dziś zdecydowanie za mało. Technologia to niezbędna podstawa, ale zaledwie punkt wyjścia. To, co pozwala odnaleźć się w tak złożonych systemach, to przede wszystkim analityczny sposób myślenia i zdrowo krytyczne podejście do powierzanych zadań.
W świecie DDD i architektury high-performance kluczowa jest otwartość na nieustanną naukę. Ogromnie ważna jest również dociekliwość połączona z cierpliwością – to właśnie te cechy ratują sytuację, gdy trzeba zdiagnozować wyjątkowo ciężki, skomplikowany problem w głębi systemu.
Jest jeszcze jeden element, który w świecie IT wciąż bywa pomijany, a u nas jest absolutnie fundamentalny: zdolności interpersonalne. Projektowanie systemów to gra zespołowa. Z jednej strony ściśle współpracujemy z analitykami, z drugiej – z innymi deweloperami, a z trzeciej – z architektami czy testerami. Bez ciągłej, jasnej i otwartej komunikacji na każdym z tych frontów, nawet najlepszy technicznie inżynier nie dowiezie dobrego rozwiązania.
Dla wielu programistów praca w sektorze ubezpieczeń wciąż kojarzy się z utrzymywaniem starych systemów monolitycznych. Jak ten wywiad i Wasza codzienna praca z DDD i wysoką wydajnością łamią ten stereotyp?
To prawda, że w branży IT pokutuje mit, iż nowoczesny system po prostu musi być oparty na mikrousługach. Chciałbym to bardzo wyraźnie sprostować: mądrze zaprojektowany monolit absolutnie nie oznacza przestarzałego systemu. To po prostu inna architektura, precyzyjnie dobrana do konkretnej klasy problemów i wymagań biznesowych.
Często używam tu metafory motoryzacyjnej. To tak, jakby powiedzieć, że tylko auto sportowe jest nowoczesne, a samochód ciężarowy nowoczesny być nie może. Nasz system to potężna, zaawansowana technologicznie maszyna. Nieustannie korzystamy z nowych, choć rygorystycznie sprawdzonych technologii, na bieżąco wykonujemy upgrade'y bibliotek i z żelazną konsekwencją stosujemy wzorce projektowe.
Co więcej, jeśli chodzi o codzienny warsztat pracy, jesteśmy daleko w przodzie. Od ponad roku na dużą skalę wykorzystujemy narzędzia oparte na sztucznej inteligencji. Zaczynaliśmy od GitHub Copilot, dziś pracujemy z Augment Code, a na co dzień wspomagamy się też takimi rozwiązaniami jak Gemini czy NotebookLM. Prowadzimy wiele wewnętrznych inicjatyw AI, by zrealizować jeden cel: pracować mądrzej, a nie ciężej.
Pielęgnujemy kulturę dzielenia się wiedzą, na co dowodem jest choćby nasz wewnętrzny Program Wymiany Wiedzy dla programistów. To wszystko sprawia, że odchodzimy od klasycznego, rzemieślniczego „klepania kodu”. W naszym zespole programista staje się pełnoprawnym projektantem i architektem rozwiązań. I to jest najlepszy dowód na to, że dzisiejsze systemy ubezpieczeniowe to nie żaden skansen, ale fascynujący i inżynieryjnie wymagający świat.
Sprawdź też nasz artykuł o tym, jak zmieniamy architekturę Comarch, by dogonić Dolinę Krzemową.