Fiszki Online Wzorce architektoniczne (Preview)
Darmowy podgląd 15 z 47 dostępnych pytań
Architektura warstwowa
Czym jest architektura warstwowa i jakie są trzy podstawowe warstwy aplikacji biznesowej?
Architektura warstwowa to technika dekompozycji systemu na poziomy ułożone jeden nad drugim, w której każda warstwa korzysta z usług warstwy znajdującej się bezpośrednio pod nią. Trzy podstawowe warstwy aplikacji biznesowej to:
- Prezentacja (presentation) — obsługa interakcji ze światem zewnętrznym: wyświetlanie informacji i tłumaczenie poleceń użytkownika (kliknięć, żądań HTTP) na operacje wykonywane niżej.
- Logika domenowa (domain / business logic) — właściwa „praca" aplikacji: obliczenia, walidacje, reguły biznesowe oraz decydowanie, jakie operacje na danych uruchomić.
- Źródło danych (data source) — komunikacja z bazą, kolejkami komunikatów i innymi systemami; najczęściej trwałe przechowywanie danych.
flowchart TD
P["Prezentacja"] --> D["Logika domenowa"]
D --> S["Źródło danych"]
S --> DB[("Baza danych")]
Warto odróżnić warstwę (layer) od tier — „tier" sugeruje rozdział fizyczny na osobne maszyny, podczas gdy warstwy to podział logiczny: wszystkie trzy mogą działać w jednym procesie na jednym komputerze.
Dziś odpowiada to typowemu backendowi: prezentacja to kontrolery REST/GraphQL/MVC, logika domenowa to serwisy i model domenowy, a źródło danych to repozytoria i ORM.
↑ Powrót na góręOrganizacja logiki domenowej
Na czym polega wzorzec Transaction Script i kiedy jest dobrym wyborem?
Transaction Script organizuje logikę biznesową proceduralnie: jedna procedura („skrypt") obsługuje pojedyncze żądanie z prezentacji — pobiera dane, waliduje, liczy, zapisuje do bazy i odsyła odpowiedź. Wspólne fragmenty wydziela się do podprocedur. Granice transakcji są oczywiste: początek i koniec skryptu.
Zalety: prostota, znajomy model proceduralny, niski próg wejścia, naturalna współpraca z relacyjną bazą (przez Row Data Gateway lub Table Data Gateway).
Wada: przy rosnącej złożoności logika degeneruje się w duplikację i „splątaną sieć procedur" — trudność rośnie niemal wykładniczo. Dlatego nadaje się do prostych przypadków (np. katalog z koszykiem i bazowym cennikiem).
Dziś to proceduralne, często „anemiczne" warstwy serwisowe — cała logika w metodzie klasy *Service, a encje pełnią rolę nośników danych.
Czym jest Domain Model i jakie wiążą się z nim koszty?
Domain Model to sieć powiązanych obiektów łączących dane i zachowanie, gdzie każdy obiekt odpowiada znaczącemu pojęciu domeny. Logika jest rozproszona na obiekty, które jej dotyczą; zamiast rozbudowywać warunki if/else, dodaje się nowe obiekty-strategie (polimorfizm). Złożoność przenosi się z algorytmów do relacji między obiektami.
Z modelem wiążą się dwa koszty:
- krzywa uczenia — wymaga „przesunięcia paradygmatu"; źle zrobiony model bywa katastrofą,
- złożone mapowanie obiektowo-relacyjne — stąd cały zestaw wzorców O/R.
Mimo to świetnie radzi sobie ze złożoną, często zmieniającą się logiką: po opanowaniu nawet proste problemy robi się łatwo, a koszt rośnie łagodnie wraz ze złożonością.
Dziś to bogate encje JPA/Hibernate z prawdziwą logiką w metodach (nie anemiczne) oraz podejście DDD.
↑ Powrót na góręWzorce dostępu do danych
Czym jest Active Record i kiedy się sprawdza?
Active Record to obiekt opakowujący wiersz tabeli, który łączy dostęp do bazy z logiką domenową — obiekt „wie, jak się zapisać i wczytać". Klasy odwzorowują strukturę tabel niemal jeden do jednego (schemat izomorficzny), mają statyczne findery oraz metody insert/update.
Główna zaleta to prostota — łatwo to zbudować i zrozumieć. Sprawdza się przy niezbyt złożonej logice działającej w obrębie pojedynczego rekordu (CRUD, proste walidacje i obliczenia) oraz gdy schemat bazy jest zbliżony do modelu.
Wady: sprzęga projekt obiektów z projektem bazy, a gdy logika rośnie (relacje, kolekcje, dziedziczenie), wzorzec zaczyna się sypać — wtedy przechodzi się na Data Mapper.
Dziś to dokładnie Rails ActiveRecord, Laravel Eloquent, Django ORM czy Sequelize.
↑ Powrót na góręActive Record kontra Data Mapper — kiedy który?
Decyduje złożoność logiki oraz zgodność modelu ze schematem.
| Kryterium | Active Record | Data Mapper |
|---|---|---|
| Złożoność logiki | niska/średnia | wysoka |
| Zgodność klas i tabel | izomorficzna | rozjeżdżająca się |
| Zależność modelu od bazy | sprzęga model z bazą | model nieświadomy bazy |
| Testy bez bazy | trudniejsze | łatwe (podmiana warstwy) |
| Złożoność implementacji | niska | wysoka (zwykle gotowe narzędzie) |
Typowa ewolucja: Row Data Gateway → (dodanie logiki) → Active Record → (rosnąca złożoność) → Data Mapper. Active Record wybierzemy, gdy model jest prosty, a baza pod kontrolą zespołu; Data Mapper, gdy model jest bogaty albo mapujemy do istniejącej, niezależnej bazy.
Dziś Active Record to Rails/Eloquent, a Data Mapper to Hibernate/JPA.
↑ Powrót na góręWzorce behawioralne mapowania O/R
Czym jest Unit of Work i jaki problem rozwiązuje?
Unit of Work śledzi wszystkie obiekty odczytane i zmienione w ramach transakcji biznesowej (zbiory: nowe, zmienione, usunięte), a przy commit() zapisuje całość w jednej transakcji systemowej — we właściwej kolejności (integralność referencyjna) i z kontrolą współbieżności.
Rozwiązuje to dwa problemy: zapisywanie przy każdej zmianie powodowałoby mnóstwo drobnych zapytań i wymagałoby trzymania otwartej transakcji przez cały czas interakcji. Tutaj programista nie wywołuje zapisów jawnie — cała wiedza o tym, co i w jakiej kolejności zapisać, jest w jednym miejscu. Rejestracja obiektów może być jawna (przez wołającego), wbudowana w obiekt lub realizowana przez kontroler śledzący odczyty.
sequenceDiagram
participant K as Kod aplikacji
participant U as Unit of Work
participant DB as Baza
K->>U: registerNew(zamówienie)
K->>U: registerDirty(klient)
K->>U: registerRemoved(pozycja)
K->>U: commit()
U->>DB: BEGIN
U->>DB: INSERT zamówienie
U->>DB: UPDATE klient
U->>DB: DELETE pozycja
U->>DB: COMMIT
Dziś to sesja Hibernate / EntityManager w JPA (persistence context) oraz DbContext w EF — z automatycznym wykrywaniem zmian (dirty checking), porządkowaniem operacji i batchowaniem.
Czym jest problem N+1 (ripple loading) i jak go rozwiązać?
Problem N+1 powstaje, gdy wypełnimy kolekcję obiektami Lazy Load i zaczniemy po niej iterować: jedno zapytanie pobiera listę, a potem każdy element wywołuje osobne zapytanie o swoje dane — łącznie 1 + N zapytań. Przy większych kolekcjach to zabija wydajność.
Rozwiązanie polega na zmianie ziarnistości lenistwa: nie buduj kolekcji z leniwych obiektów, lecz uczyń całą kolekcję leniwą i wczytuj jej zawartość jednym zapytaniem (zwykle przez JOIN). Reguły praktyczne:
- pobieraj wiele wierszy naraz; nigdy nie odpytuj tej samej tabeli wiersz po wierszu,
- łącz dane wielu tabel jednym zapytaniem; około 3–4 złączeń to zwykle optimum,
- zawsze profiluj na docelowej bazie i danych.
Dziś remedium to JOIN FETCH, @BatchSize, grafy encji (@EntityGraph) oraz świadome strategie pobierania w JPA.
Wzorce strukturalne mapowania O/R
Jakie są strategie mapowania dziedziczenia na tabele i czym się różnią?
| Strategia | Na czym polega | Zalety | Wady |
|---|---|---|---|
| Single Table | cała hierarchia w jednej tabeli + kolumna-dyskryminator | brak złączeń przy odczycie, łatwa refaktoryzacja pól | puste kolumny (marnowane miejsce), tabela rośnie i staje się wąskim gardłem |
| Class Table | tabela na każdą klasę w hierarchii | brak marnowania miejsca, czysta zgodność model–baza | wiele złączeń na odczyt jednego obiektu, nadtabela jako wąskie gardło |
| Concrete Table | tabela na każdą klasę konkretną z polami przodków | brak złączeń, samowystarczalne tabele | trudne klucze (unikalność w całej hierarchii), zmiana nadklasy = zmiana wszystkich tabel, wyszukiwanie po nadtypie przeszukuje wszystkie tabele |
Wzorce można mieszać w obrębie jednej hierarchii, kosztem złożoności. Wszystkie trzy opierają się na wspólnej strukturze mapperów dziedziczenia, która porządkuje łańcuchowe wczytywanie i zapis nad- oraz podklas.
Dziś odpowiadają strategiom JPA: SINGLE_TABLE, JOINED i TABLE_PER_CLASS.
Mapowanie metadanych i zapytania
Czym jest Repository i jak ma się do Query Object?
Repository pośredniczy między domeną a warstwą mapującą, udostępniając interfejs przypominający kolekcję obiektów w pamięci. Klient buduje deklaratywnie kryterium (specyfikację) i prosi repozytorium o pasujące obiekty — nie ma pojęcia „wykonania zapytania" ani SQL; fakt, że obiekty pochodzą z bazy, jest ukryty.
Różnica wobec Query Object: przy bezpośrednim użyciu Query Object klient dodaje kryteria i wykonuje zapytanie; przy Repository przekazuje kryteria i pyta o obiekty spełniające specyfikację — interakcja jest bardziej deklaratywna i obiektowa. Pod spodem Repository łączy Metadata Mapping z Query Object.
Repository zastępuje rozproszone, wyspecjalizowane findery oraz ułatwia podmianę źródła — np. strategię relacyjną na działającą w pamięci, co przyspiesza testy.
Dziś to wręcz kanoniczne Spring Data (JpaRepository, Specification, metody pochodne) oraz repozytoria w DDD.
Prezentacja webowa
Czym jest MVC i jakie są najczęstsze nieporozumienia wokół niego?
MVC dzieli interakcję z UI na trzy role:
- Model — dane i zachowanie domeny, bez maszynerii UI,
- View — wyłącznie wyświetlanie modelu,
- Controller — przyjmuje wejście użytkownika, manipuluje modelem i powoduje aktualizację widoku.
Wzorzec niesie dwie separacje. Ważniejsza to rozdział prezentacji od modelu: model nie zależy od prezentacji, można mieć wiele prezentacji jednego modelu, a obiekty niewizualne łatwiej testować (w rich client wiele jednoczesnych widoków obsługuje wzorzec Observer). Mniej istotna jest separacja widoku od kontrolera.
Najczęstsze nieporozumienie to mylenie kontrolera z MVC z Application Controllerem — warstwą sterującą przepływem między ekranami. To zupełnie inny byt, tyle że nazwany podobnie.
Dziś to Spring MVC, ASP.NET MVC i Rails; rolę Observera pełni reaktywne wiązanie danych (stan w React, sygnały).
↑ Powrót na góręWspółbieżność
Optimistic a pessimistic concurrency control — kiedy który?
- Optimistic (wykrywanie konfliktu) — wszyscy edytują swobodnie własną kopię, a kontrola następuje dopiero przy zapisie. Pierwszy commit przechodzi, kolejny dostaje konflikt i musi sobie poradzić.
- Pessimistic (zapobieganie konfliktowi) — kto pierwszy sięgnie po dane, blokuje innych aż do zakończenia.
Optymistyczny daje lepszy liveness i jest łatwiejszy, ale konflikt wykrywa na końcu, co grozi utratą pracy (scalanie danych biznesowych często jest niemożliwe). Pesymistyczny ogranicza współbieżność, jest trudniejszy i podatny na zakleszczenia.
Esencja wyboru to częstotliwość i koszt konfliktów: rzadkie i tanie → optymistyczny (wybór domyślny); częste lub bardzo bolesne → pesymistyczny, stosowany selektywnie jako uzupełnienie.
Dziś optymistyczny realizuje pole @Version w JPA, a pesymistyczny — SELECT ... FOR UPDATE / LockModeType.PESSIMISTIC_*.
Stan sesji
Dlaczego bezstanowość serwera jest cenna i kiedy stan jest konieczny?
Serwer bezstanowy nie pamięta stanu między żądaniami, więc nie ma znaczenia, który obiekt czy która instancja obsłuży dane żądanie. Dzięki temu obiekty można trzymać w puli, a niewielka ich liczba obsługuje wielu — zwłaszcza bezczynnych — użytkowników. Pasuje to do bezstanowego HTTP i ułatwia skalowanie poziome.
Jednak wiele interakcji jest z natury stanowych — klasyczny przykład to koszyk, który musi przetrwać całą sesję. Stanu nie da się uniknąć; trzeba zdecydować, gdzie go przechowywać. Stan sesji ma cechy transakcyjne i bywa w trakcie edycji chwilowo niepoprawny — reguły walidacji obowiązują dopiero przy zatwierdzeniu.
Dziś to bezstanowe API skalowane poziomo, ze stanem trzymanym w podpisanym cookie/tokenie lub w zewnętrznym magazynie (np. Redis).
↑ Powrót na góręDystrybucja
Co mówi „pierwsze prawo projektowania obiektów rozproszonych" i dlaczego interfejsy zdalne muszą być inne?
Pierwsze prawo brzmi: nie rozpraszaj obiektów. Rozbijanie modelu na osobne węzły „dla skalowania" zwykle rujnuje wydajność i utrudnia rozwój. Powód jest fizyczny: wywołanie w obrębie procesu jest bardzo szybkie, międzyprocesowe wolniejsze o rzędy wielkości, a międzymaszynowe jeszcze bardziej.
Dlatego interfejs używany zdalnie musi być gruboziarnisty (jedno wywołanie pobiera lub zapisuje komplet danych), a nie drobnoziarnisty (dziesiątki małych metod get/set), jak interfejs lokalny.
sequenceDiagram
participant K as Klient
participant S as Serwer
Note over K,S: Interfejs drobnoziarnisty (źle przez sieć)
K->>S: getCity()
K->>S: getStreet()
K->>S: getZip()
Note over K,S: Interfejs gruboziarnisty (dobrze)
K->>S: getAddressDetails()
Lepsze niż rozpraszanie jest zwykle klastrowanie: wszystkie klasy w jednym procesie i wiele kopii tego procesu. Dziś to przestroga przed rozdrabnianiem aplikacji na mikrousługi „dla skalowania"; SQL z kolei celowo zaprojektowano jako interfejs gruboziarnisty.
↑ Powrót na góręCzym jest Data Transfer Object (DTO) i po co assembler?
DTO to obiekt przenoszący komplet danych między procesami jednym wywołaniem, by zredukować liczbę kosztownych rund po sieci. Niesie zwykle więcej danych, niż akurat potrzeba (lepiej przesłać za dużo niż wykonać wiele wywołań), agreguje dane z wielu obiektów, jest serializowalny i występuje po obu stronach połączenia; jego pola to typy proste, łańcuchy znaków lub inne DTO.
Encji domeny nie wysyła się po sieci — są spięte złożoną siecią referencji i nie chcemy mieć klas domeny po stronie klienta. Aby DTO i domena pozostały niezależne, konwersję wykonuje osobny assembler (przykład wzorca Mapper): tworzy DTO z modelu i aktualizuje model z DTO.
DTO idzie w parze z Remote Facade: fasada daje gruboziarniste metody, a DTO — gruboziarnisty transfer danych.
Dziś to klasy DTO/ViewModel/response models oraz komunikaty proto; rolę assemblerów pełnią narzędzia takie jak MapStruct czy AutoMapper, a żelazną zasadą jest nieserializowanie encji ORM po sieci.
↑ Powrót na góręWzorce bazowe
Czym jest Value Object i dlaczego powinien być niezmienny?
Value Object to mały obiekt porównywany przez wartości pól, a nie przez tożsamość — dwie daty są równe, gdy mają te same składowe. To odróżnia go od obiektu referencyjnego, porównywanego przez tożsamość (lub klucz główny w bazie).
Value Object powinien być niezmienny, by uniknąć błędów aliasowania: gdyby współdzielony obiekt zmienił się u jednego właściciela, niespodziewanie zmieniłby się też u drugiego. Zmiana wartości polega więc na utworzeniu nowego obiektu. Trzeba przesłonić metody równości i skrótu (equals/hashCode) na podstawie pól. Utrwala się go zwykle jako Embedded Value.
Dziś to record w Javie i C#, data class w Kotlinie, @Embeddable w JPA oraz value objects w DDD. Uwaga na kolizję nazw: w dawnym świecie J2EE „value object" oznaczało to, co dziś nazywamy DTO.