Monolity

Baza pytań rekrutacyjnych i wiedzy. Filtruj, szukaj i sprawdzaj swoją wiedzę.

Tematy
easymonolitharchitecturedeployment

Odpowiedź

Architektura monolityczna to pojedyncza wdrażalna aplikacja, w której moduły działają w jednym procesie i zwykle współdzielą jedną bazę danych. System buduje się, testuje i wdraża jako całość, co jest proste na początku, ale może prowadzić do silnych powiązań przy wzroście.

easymonolithtradeoffsscalability

Odpowiedź

Zalety: prostszy rozwój i wdrożenia, łatwe testowanie/debugowanie lokalne oraz silna spójność i proste transakcje. Wady: trudniej niezależnie skalować części systemu, wolniejsze cykle wdrożeń i rosnący kod może stać się silnie powiązany i trudniejszy do bezpiecznych zmian.

mediummodular-monolithboundariesmaintainability

Odpowiedź

Utrzymuj jasne granice modułów (np. domenowe), egzekwuj zasady zależności, utrzymuj cienkie warstwy i dodawaj testy automatyczne. Stosuj wewnętrzne API, ograniczaj współdzielony stan i regularnie refaktoruj, aby monolit pozostał modułowy i łatwy w zmianach.

mediummodular-monolitharchitectureevolution

Odpowiedź

Monolit modułowy to wciąż jedna jednostka wdrożeniowa, ale z silną separacją modułów. Każdy moduł posiada własną domenę i komunikuje się przez dobrze zdefiniowane interfejsy, co poprawia utrzymanie i ułatwia przyszłe wydzielanie usług.

hardmigrationstrangler-figmicroservices

Odpowiedź

Preferuj migrację krokową (Strangler Fig): wydzielaj po jednej funkcji/domenie za stabilnym API i dodaj dobre logi/metryki/tracing. Unikaj big‑bang rewrite’u; rozdzielaj dane i logikę stopniowo.

Odpowiedź

Monolit to jedna jednostka wdrożeniowa (jedna aplikacja/serwis) zawierająca wiele modułów/funkcji. Często jest świetny na start, bo upraszcza development, testy i wdrożenia oraz ułatwia transakcje i debugowanie.

Odpowiedź

Gdy zespół jest mały, domena szybko się zmienia i chcesz szybko iterować przy prostych operacjach. Mikroserwisy zwykle opłacają się dopiero, gdy realnie potrzebujesz niezależnych wdrożeń/skalowania i masz jasne granice.

easymodular-monolithboundariesarchitecture

Odpowiedź

To wciąż jedna jednostka wdrożeniowa, ale z mocnymi granicami wewnętrznymi: moduły posiadają swoją domenę i komunikują się przez jasno zdefiniowane interfejsy. To poprawia utrzymanie i ułatwia przyszłe wydzielanie serwisów.

mediummaintainabilitymodularityrefactoring

Odpowiedź

Ustal jasne granice modułów (często domenowe), egzekwuj zasady zależności i utrzymuj cienkie warstwy. Dodaj testy automatyczne i regularnie refaktoruj, żeby kod pozostał modułowy i łatwy do zmian.

mediumscalingstatelessload-balancer

Odpowiedź

Zrób aplikację stateless (sesje w Redis/DB), uruchom wiele instancji za load balancerem i osobno skaluj bazę (indeksy, cache, read repliki). Zwróć uwagę na współdzielone zasoby jak pliki i joby w tle.

Odpowiedź

Feature flagi pozwalają włączać/wyłączać funkcje w runtime bez wdrożenia. Ułatwiają bezpieczne release’y (dark launch, rollout) i szybki rollback, ale wymagają sprzątania, żeby nie mieć „flag debt”.

hardstrangler-figmigrationmicroservices

Odpowiedź

Dodaj warstwę routingu, wybierz małą funkcję/domenę, wydziel ją za stabilnym API i stopniowo przełączaj ruch. Powtarzaj kroki „po plasterku” z monitoringiem i rollbackiem, aż stara ścieżka będzie do usunięcia.

Odpowiedź

Własność danych i spójność: kto jest właścicielem których tabel i jak utrzymać spójność w trakcie przejścia (dual writes/outbox/eventy). Migracja powinna być krokowa z jasnym momentem cutover i strategią rollback.

Odpowiedź

Szukaj bounded contexts: funkcji z jasnym właścicielem, danymi i małą liczbą zależności. Zacznij od części, które często się zmieniają lub mają wyraźne potrzeby skalowania, i nie dziel na początku mocno sprzężonych fragmentów.

harddistributed-monolithcouplingmicroservices

Odpowiedź

To system podzielony na serwisy, ale nadal mocno sprzężony (wspólna baza, synchroniczne „gadatliwe” wywołania, skoordynowane wdrożenia). Unikasz przez jasną własność, asynchroniczność tam gdzie trzeba, stabilne kontrakty i niezależne wdrożenia.

easymonorepomonolithrepo-structure

Odpowiedź

Monorepo to strategia repozytorium (wiele projektów w jednym repo). Monolit to jednostka wdrożeniowa/runtime (jedna aplikacja). Możesz mieć monolit w monorepo albo mikroserwisy w monorepo.

mediummaintainabilitycouplingcode-smell

Odpowiedź

To kod bez jasnych granic i z dużą, przypadkową zależnością między częściami. Objawy: brak jasnej odpowiedzialności, losowe regresje po zmianach, dużo globalnego stanu i „wszystko zależy od wszystkiego”.

mediummodulesboundariesarchitecture-tests

Odpowiedź

Organizuj kod po funkcjach/domenach, zdefiniuj jasne publiczne API między modułami i ogranicz zależności (np. reguły pakietów, testy architektury). Trzymaj wspólne utilsy małe i unikaj „god modułów”.

Odpowiedź

Zastosuj expand/contract: najpierw dodaj nowy schemat (np. nullable kolumna/nowa tabela) i wdroż kod obsługujący stare i nowe; potem zmigruj dane; a na końcu usuń stare w kolejnej wersji. To minimalizuje downtime i wspiera rollback.

Odpowiedź

Skaluj poziomo (wiele stateless instancji za load balancerem) i przenieś ciężkie zadania do asynchronicznych jobów/kolejek (background processing). Dodatkowo skaluj odczyty przez cache i repliki.

Odpowiedź

Package-by-layer grupuje kod po warstwach technicznych (kontrolery/serwisy/repo). Package-by-feature grupuje kod po funkcji/domenie. Struktura feature-based często lepiej skaluje, bo powiązany kod jest razem i granice są czytelniejsze.

Odpowiedź

Użyj osobnego procesu workera (ta sama baza kodu, inny entrypoint) konsumującego kolejkę, z retry i idempotencją. To nie blokuje requestów web i daje lepszą kontrolę współbieżności oraz błędów.

Odpowiedź

Trzymaj utilsy blisko funkcji, która jest ich właścicielem, a wspólny kod wydzielaj dopiero, gdy realnie potrzebuje go wiele modułów. Preferuj małe, nazwane biblioteki ze znanym właścicielem zamiast jednego ogromnego `utils`.

Odpowiedź

Użyj cache (zależności i artefaktów builda), uruchamiaj testy/linty inkrementalnie dla zmienionych modułów i równoleglij joby. Pomaga też niezależność modułów, żeby nie przebudowywać wszystkiego po małej zmianie.

Odpowiedź

Rób to krokowo: wybierz jedną granicę, dodaj testy wokół zachowania, refaktoruj za stabilnym interfejsem i wypuszczaj małe kroki. Planuj czas na dług techniczny i unikaj big-bang rewrite’u.

Odpowiedź

Single deployable oznacza, że wdrażasz jeden artefakt jako jedną całość (jedna wersja do zbudowania, przetestowania i wdrożenia). To upraszcza release’y i rollbacki oraz unika niedopasowania wersji między serwisami. Minusem jest większy blast radius, gdy coś pójdzie źle.

Odpowiedź

Trzymaj piramidę testów: dużo szybkich unit testów, mniej integracyjnych i mało E2E. W integracji testuj kluczowe “seamy” (DB, messaging) z realistycznymi zależnościami (np. Testcontainers) i dbaj o równoległość oraz stabilność. Unikaj jednego gigantycznego test-suite “testuje wszystko”.

mediummonolithmodularityboundaries+1

Odpowiedź

Organizuj kod po funkcjach/domenach (a nie tylko po warstwach technicznych), wystawiaj małe wewnętrzne API między modułami i zabraniaj “sięgania” do wnętrza innych modułów importami. Dodaj ownership (kto utrzymuje co) i checki architektoniczne (granice modułów), żeby granice nie rozjeżdżały się w czasie.

Odpowiedź

Typowe opcje: osobna baza per tenant (mocna izolacja, większy koszt), osobna schema per tenant (dobra izolacja, średnia złożoność) albo współdzielone tabele z `tenant_id` (najtańsze, najtrudniejsze do poprawnego egzekwowania). Niezależnie od podejścia musisz wszędzie wymuszać tenant scoping oraz dodać właściwe indeksy i checki bezpieczeństwa.

hardmonolithperformanceobservability+1

Odpowiedź

Zdefiniuj SLI wydajności (np. p95) i monitoruj je cały czas. Dodaj profiling/tracing dla wolnych endpointów, rób load testy krytycznych flow i ustaw budżety/alerty, żeby łapać regresje wcześnie. Feature flagi pomagają szybko wycofać zmianę, gdy trzeba.

Odpowiedź

Structured logging oznacza, że logi mają stałe pola (np. JSON) typu `level`, `message`, `requestId`, `userId`. Pomaga, bo możesz je łatwo filtrować, wyszukiwać i łączyć między wieloma fragmentami kodu bez parsowania „losowego” tekstu.

Odpowiedź

Correlation ID (request ID) to unikalny identyfikator requestu, który trafia do logów. Generuj go na wejściu (middleware/filter HTTP) albo przyjmij z upstreamu, a potem przekazuj przez wszystkie warstwy oraz joby uruchomione przez ten request.

Odpowiedź

Zdefiniuj granice modułów i reguły kierunku zależności (np. moduły funkcjonalne mogą zależeć od shared kernel, ale nie od siebie nawzajem). Wymuszaj to buildem (osobne moduły Gradle/Maven), testami architektury oraz wystawianiem stabilnych interfejsów/fasad zamiast sięgania do „wnętrzności”.

Odpowiedź

Pomagają rozsprzęgać moduły: jeden moduł publikuje event („OrderPlaced”), a inne reagują bez ścisłych zależności. Pułapki: decyzja sync vs async, niewrzucanie ciężkiej pracy do tej samej transakcji oraz niezawodność i idempotencja handlerów (event może się powtórzyć).

Odpowiedź

Traktuj tabele jako „własność” modułów: tylko właściciel zapisuje dane i wystawia dostęp przez swoje API/fasadę. Unikaj cross-module joinów „gdzie popadnie”; zamiast tego pobieraj dane przez moduł właściciela albo użyj domain events. Jeśli trzeba, wymuszaj to osobnymi schematami, granicami repozytoriów oraz code review/regułami architektury.