Fiszki Online Architektura oprogramowania (Preview)
Darmowy podgląd 15 z 107 dostępnych pytań
Fundamenty i analiza trade-offów
Dlaczego w nowoczesnej architekturze rozproszonej nie ma uniwersalnych „best practices"?
Programista zwykle rozwiązuje problemy powtarzalne — konfigurację narzędzia czy błąd frameworka da się znaleźć na Stack Overflow. Architekt pracuje inaczej: każdy istotny problem jest splotem konkretnej organizacji, jej historii, zespołów i ograniczeń technicznych, więc rzadko ktokolwiek na świecie rozwiązał dokładnie ten sam przypadek. Stąd o architekturze powstaje znacznie mniej „przepisów" niż o API.
Konsekwencja praktyczna: nie istnieje „najlepszy" projekt, bo „najlepszy" zakładałby jednoczesną maksymalizację wszystkich konkurujących cech (wydajności, skalowalności, odporności, prostoty), co jest niemożliwe. Realnym celem jest najmniej zła kombinacja kompromisów (least worst combination of trade-offs) — taka, w której żadna cecha nie błyszczy w izolacji, ale całość sprzyja powodzeniu projektu.
Dlatego rdzeniem pracy architekta nie jest znajomość gotowych wzorców, lecz podejmowanie decyzji: obiektywne wyważanie kompromisów po obu stronach każdego wyboru. To umiejętność ponadczasowa — konkretne technologie (orkiestrowane SOA → mikroserwisy → kontenery → Kubernetes) wymieniają się co kilka lat, a metoda oceny trade-offów zostaje.
↑ Powrót na góręCzym jest Architecture Decision Record (ADR)?
ADR to krótki dokument (zwykle 1–2 strony, w Markdown lub AsciiDoc, trzymany w repo obok kodu) opisujący jedną istotną decyzję architektoniczną. Spopularyzował go Michael Nygard. To najprostszy skuteczny sposób, by decyzje nie żyły wyłącznie w głowach i mailach.
Typowy ADR ma trzy sekcje:
- Context (kontekst) — na czym polega problem i jakie były rozważane alternatywy.
- Decision (decyzja) — co postanowiono, wraz z uzasadnieniem dlaczego.
- Consequences (konsekwencje) — jakie są skutki wyboru i jakie trade-offy świadomie zaakceptowano.
Wartość ADR-ów rośnie w czasie: nowy członek zespołu czyta nie tylko jak coś zbudowano, ale dlaczego — a druga zasada architektury mówi, że „dlaczego" jest ważniejsze niż „jak". ADR dokumentuje decyzję; jej egzekwowaniem zajmują się już fitness functions.
↑ Powrót na góręCoupling i kwant architektoniczny
Czym jest architecture quantum (kwant architektoniczny)?
Kwant architektoniczny to niezależnie wdrażalny artefakt o wysokiej spójności funkcjonalnej i wysokim sprzężeniu statycznym wewnątrz, sprzężony dynamicznie (synchronicznie) na zewnątrz. To miara łącząca topologię i zachowanie systemu — pozwala mówić jednym językiem o granicach wdrożeniowych, zakresie domeny i sprzężeniach.
Trzy składowe definicji:
| Składowa | Co oznacza |
|---|---|
| Independently deployable | Każdy kwant to osobna jednostka wdrożeniowa |
| High functional cohesion | Elementy realizują jeden spójny workflow domenowy (zbieżne z bounded context z DDD) |
| High static coupling | Wszystko jest ze sobą „okablowane" w spójną całość |
Praktyczne wnioski: monolit jest zawsze jednym kwantem (jeden deployment + zwykle jedna baza). Dobrze uformowany mikroserwis z własną bazą to osobny kwant — może mieć własne charakterystyki (inną skalowalność, inny poziom bezpieczeństwa). Pojęcie kwantu jest punktem wyjścia do całej analizy sprzężeń w systemie rozproszonym.
↑ Powrót na góręCzym różni się static coupling od dynamic coupling?
To dwa prostopadłe spojrzenia na sprzężenie, oba potrzebne.
Static coupling opisuje, jak rzeczy są ze sobą okablowane — czyli zależności potrzebne, by serwis w ogóle wystartował: system operacyjny, frameworki i biblioteki (także tranzytywne), baza danych, broker komunikatów, orkiestracja kontenerów. Pytanie diagnostyczne: „czego potrzeba, żeby uruchomić ten serwis od zera?". Samo istnienie brokera to sprzężenie statyczne.
Dynamic coupling opisuje, jak serwisy wołają się nawzajem w czasie działania — synchronicznie czy asynchronicznie, jak ścisły jest kontrakt, jaką spójność wymuszają. Wywołanie przez broker to sprzężenie dynamiczne.
Rozróżnienie ma praktyczne skutki. Diagram sprzężeń statycznych służy do analizy ryzyka i niezawodności („jeśli zmienię X, co się zepsuje?") — dane do niego można pozyskać z manifestów kontenerów, plików POM (Maven), zależności NPM i grafu wywołań z observability. Diagram dynamiczny służy do projektowania workflow i jego trade-offów wydajności i spójności.
↑ Powrót na góręModularność architektoniczna
Czym różni się scalability od elasticity i czym jest MTTS?
To dwie różne cechy, często mylone:
- Scalability (skalowalność) — utrzymanie responsywności przy stopniowym wzroście obciążenia w czasie (np. z 1000 do 10 000 użytkowników w ciągu miesięcy).
- Elasticity (elastyczność) — responsywność przy nagłych, gwałtownych skokach (np. system biletowy: 20 → 3000 użytkowników w kilka sekund po starcie sprzedaży).
Co ważne, zależą od różnych rzeczy. Skalowalność jest funkcją modularności (rozbicia na osobne jednostki), a elastyczność — funkcją granularności (rozmiaru jednostki), bo zależy od małego MTTS (mean time to startup) — czasu, w jakim nowa instancja staje się gotowa. Drobne serwisy na lekkim runtime startują w sekundy; monolit z długim MTTS nie nadąży za skokiem.
Stąd: monolit warstwowy skaluje się słabo i kosztownie (cała aplikacja skaluje się tak samo), mikroserwisy dają maksymalną skalę i elastyczność. Trade-off: im więcej komunikacji synchronicznej w jednej transakcji, tym gorsza skala — przy wysokich wymaganiach minimalizuj wywołania synchroniczne.
↑ Powrót na góręDekompozycja monolitu
Jak ocenić, czy kod nadaje się do dekompozycji?
Pierwszy krok każdej migracji to ustalić, czy w ogóle warto i czy się da. Skrajnym przeciwieństwem struktury jest Big Ball of Mud — kod bez wewnętrznych granic (np. obsługa zdarzeń wpięta wprost w wywołania bazy). Architektura zajmuje się strukturą; jeśli jej nie ma, nie ma czego rozdzielać.
Żadna pojedyncza metryka nie rozstrzyga, ale metryki sprzężeń dają makro-obraz. Kluczowe są afferent i efferent coupling (Yourdon i Constantine):
- Afferent coupling (CA) — sprzężenia przychodzące: ile innych elementów zależy od danego komponentu.
- Efferent coupling (CE) — sprzężenia wychodzące: od ilu innych elementów dany komponent zależy.
Przykład praktyczny: współdzielona klasa Address jest w monolicie zachętą do reuse, ale przy podziale trzeba znać jej afferent coupling — ile części systemu ją wywołuje — bo to przesądza o koszcie rozplątania. Narzędzia jak JDepend liczą te metryki per pakiet.
Wzorce dekompozycji komponentów
Jak ocenić wykonalność migracji na podstawie sprzężeń komponentów?
Sercem oceny jest analiza sprzężeń między komponentami (nie klas wewnątrz nich — te mogą być splątane dowolnie). Pomocna jest metafora rozmiaru diagramu zależności:
- golf ball — minimalne zależności → migracja wykonalna, wystarczy refaktoryzacja,
- basketball — dużo zależności (typowe dla aplikacji biznesowych) → trudniej, miks refaktoryzacji i przepisania,
- airliner — gęsta plątanina → praktycznie niewykonalne, realnie tylko przepisanie od zera.
Poziom sprzężenia komponentów jest jednym z najważniejszych czynników powodzenia — zespoły, które rzucają się na mikroserwisy bez tej analizy, najczęściej zawodzą. Sprzężenie można redukować: silnie sprzężony komponent A (CA=20) da się rozbić na A1 (mała, gęsto wołana część, CA=14) i A2 (reszta, CA=6), zmniejszając zależności. Warto też odfiltrować komponenty będące bibliotekami współdzielonymi (wiązanymi w kompilacji), żeby zobaczyć rzeczywiste zależności rdzenia. Do wymiarowania używa się obiektywnej metryki liczby statements, a nie liczby plików czy linii kodu — bo te zależą od stylu programisty.
↑ Powrót na góręRozbijanie danych operacyjnych
Jak przebiega dekompozycja monolitycznej bazy danych krok po kroku?
Punktem wyjścia jest antywzorzec shared database integration — wszystkie serwisy widzą wszystkie tabele. Celem jest data sovereignty: każdy serwis włada tylko swoimi danymi. Kluczowe pojęcie to data domain — zbiór sprzężonych artefaktów (tabel, widoków, kluczy obcych) używanych razem w wąskim zakresie funkcjonalnym; zwykle mapowany 1:1 na schemat bazy.
Pięć etapów:
- Analizuj bazę i utwórz data domains — pogrupuj powiązane tabele w domeny (np. Customer, Ticketing, Payment).
- Przypisz tabele do domen — przenieś je do schematów domen (
ALTER SCHEMA ... TRANSFER). Tymczasowo pomocne są synonimy (jak symlink do obiektu z innego schematu), które ułatwiają analizę zależności. - Rozdziel połączenia do domen — najtrudniejszy krok: każdy serwis łączy się tylko ze swoim schematem, a dostęp międzydomenowy przenosi się na poziom serwisów (serwis pyta serwis-właściciela, nie cudzą bazę). Procedury sięgające cudzych tabel trzeba przenieść do warstwy aplikacji.
- Przenieś schematy na osobne serwery — albo backup & restore (z downtime), albo replikacja (bez downtime, ale więcej pracy).
- Przełącz na niezależne serwery — przepnij połączenia i usuń stare schematy. Każda domena ma teraz własny serwer i może być optymalizowana niezależnie (w tym pod kątem typu bazy).
Granularność serwisów
Kiedy dzielić serwis na drobniejsze — jakie są granularity disintegrators?
Najpierw rozróżnienie: modularność to czy rozbijamy system, granularność to jak duże są części. Większość problemów systemów rozproszonych dotyczy granularności, nie modularności. „Micro" w microservices nie znaczy „rozbij wszystko".
Sześć powodów, by zejść z granularnością niżej:
- Service scope and function — gdy serwis robi rzeczy słabo ze sobą powiązane (single responsibility). Notification (SMS/email/list) ma silną spójność — nie dziel; „customer" łączący profil, preferencje i komentarze ma słabą — dziel.
- Code volatility — różne części zmieniają się w różnym tempie. Jeśli logika listów zmienia się co tydzień, a SMS/email raz na pół roku, wydzielenie zmiennej części zawęża zakres testów i ryzyko wdrożeń.
- Scalability / throughput — gdy części mają różne potrzeby skali (SMS 220 000/min, list 1/min), trzymanie ich razem zmusza wszystkie do skalowania jak najgłośniejsza.
- Fault tolerance — gdy awaryjna część (email z out-of-memory) kładzie stabilne SMS i listy.
- Security — gdy część danych wymaga ostrzejszej kontroli dostępu (np. operacje na karcie kredytowej obok zwykłego profilu).
- Extensibility — gdy wiadomo, że kontekst będzie się rozrastał (np. metody płatności: karty → PayPal → ApplePay), osobne serwisy ułatwiają dokładanie kolejnych.
Złota zasada przy podziale: jeśli „resztki" nie da się sensownie nazwać (np. „Serwis nie-email"), granica jest zła.
↑ Powrót na góręKiedy łączyć serwisy — jakie są granularity integrators?
Cztery powody, by nie schodzić zbyt nisko z granularnością:
- Database transactions — gdy biznes wymaga pojedynczej jednostki pracy ACID (np. rejestracja klienta zapisuje profil i hasło atomowo). Osobne serwisy dają lepszą kontrolę dostępu, ale tracą atomowość — przy awarii jednego dane są niespójne i potrzebne są złożone kompensacje.
- Workflow and choreography — gdy serwisy muszą intensywnie ze sobą rozmawiać. Każdy hop synchroniczny dokłada latencję (sieć + bezpieczeństwo, np. ~300 ms × 5 hopów = +1500 ms) i zależność tranzytywną (awaria serwisu C kładzie wszystkich zależnych). Reguła kciuka: jeśli ~70% żądań jest atomowych a ~30% wymaga workflow — można trzymać osobno; jeśli odwrotnie — łączyć.
- Shared code — gdy serwisy współdzielą domenową bibliotekę, której częsta zmiana wymusza skoordynowany redeploy wszystkich. Wyjątek: infrastruktura przekrojowa (logowanie, audyt, autoryzacja) nie jest powodem do łączenia.
- Data relationships — gdy rozbicie serwisu wymusiłoby rozbicie wzajemnie zależnych tabel, skazując serwisy na ciągłą komunikację — lepiej je scalić.
Architekt nie rozstrzyga tego „na czuja": formułuje trade-off statements (np. „agility vs integralność danych") i decyduje je razem z product ownerem.
↑ Powrót na góręWłasność danych i transakcje rozproszone
Jak przypisać własność danych w systemie rozproszonym?
Reguła kciuka: właścicielem tabeli jest serwis, który do niej zapisuje (sam odczyt nie nadaje własności). Komplikacje pojawiają się przy wielu piszących:
- Single ownership — tylko jeden serwis zapisuje; on jest właścicielem, tabela wchodzi w jego bounded context. Te przypadki rozwiązuj najpierw, żeby oczyścić pole.
- Common ownership — bardzo wiele serwisów zapisuje do tej samej tabeli (klasyk: Audit zapisywany przez setki serwisów). Wrzucenie tego do wspólnej bazy reaktywuje wszystkie problemy współdzielenia. Rozwiązanie: dedykowany serwis-właściciel (Audit Service), do którego inni wysyłają dane — asynchronicznie fire-and-forget przez trwałą kolejkę, gdy nie trzeba odpowiedzi, albo request-reply, gdy trzeba (np. numer potwierdzenia).
- Joint ownership — kilka serwisów z tej samej domeny zapisuje do wspólnej tabeli (np. Catalog i Inventory do tabeli Product). To najbardziej złożony przypadek, rozwiązywany osobnymi technikami.
Dobre przypisanie własności jest fundamentem — bez niego nie da się ani rozbić bazy, ani sensownie zaprojektować transakcji rozproszonych.
↑ Powrót na góręJakie są wzorce osiągania eventual consistency?
Trzy główne wzorce, różniące się responsywnością i sprzężeniem:
- Background Synchronization — zewnętrzny proces (np. nocny batch) okresowo uzgadnia źródła. Dobra responsywność (użytkownik nie czeka), ale najdłuższy czas do spójności. Poważna wada: proces musi mieć dostęp zapisu do wszystkich tabel, więc łamie bounded contexts i duplikuje logikę biznesową — nieodpowiedni dla mikroserwisów, pasuje do zamkniętych, niezależnych systemów (np. zamówienia ↔ osobny system fakturowania).
- Orchestrated Request-Based — całą transakcję przetwarza się w trakcie żądania, zwykle przez dedykowany serwis-orkiestrator. Faworyzuje spójność nad responsywnością; główny ból to złożona obsługa błędów — gdy część serwisów już zacommitowała, zostają transakcje kompensujące.
- Event-Based — najpopularniejszy i najbardziej niezawodny dla mikroserwisów i EDA. Asynchroniczny pub/sub: serwisy publikują i nasłuchują zdarzenia, reagując równolegle. Krótki czas do spójności i dobra responsywność. Wymaga durable subscribers (np. RabbitMQ, ActiveMQ, AmazonMQ) lub trwałego strumienia (Apache Kafka), a błędy przetwarzania trafiają po N próbach do dead letter queue.
flowchart LR
P["Serwis publikujacy"] -->|event| T(("Topic / strumien"))
T --> C1["Serwis A (subskrybent)"]
T --> C2["Serwis B (subskrybent)"]
T --> C3["Serwis C (subskrybent)"]
C2 -.->|blad po N probach| DLQ[("Dead letter queue")]
↑ Powrót na górę
Workflow rozproszony
Czym różni się orkiestracja od choreografii?
To dwa fundamentalne style koordynacji workflow; różni je obecność lub brak centralnego koordynatora.
Orkiestracja używa komponentu orchestrator (mediator), który zna stan workflow, steruje kolejnością, obsługą błędów i powiadomieniami. W mikroserwisach jest orkiestrator na workflow, a nie jeden globalny ESB (ten tworzyłby niepożądany punkt sprzężenia). Mocna strona to obsługa błędów — orkiestrator naprawia stan, nie dodając nowych ścieżek komunikacji ponad te z happy path. Wady: orkiestrator bywa wąskim gardłem i punktem awarii, słabiej się skaluje, silniej sprzęga.
Choreografia nie ma koordynatora — serwisy reagują na zdarzenia jak tancerze znający choreografię z góry. Daje lepszą responsywność, skalowalność i odsprzężenie. Pozorna prostota pryska jednak przy błędach: każdy scenariusz błędu zmusza serwisy do dodatkowej komunikacji (compensating messages), której happy path nie potrzebował.
flowchart TB
subgraph O["Orkiestracja"]
OR(["Orchestrator"])
OR --> A1["Serwis A"]
OR --> A2["Serwis B"]
OR --> A3["Serwis C"]
end
subgraph C["Choreografia"]
B1["Serwis A"] --> B2["Serwis B"] --> B3["Serwis C"]
end
Reguła: im bardziej złożony workflow (warunki brzegowe, błędy), tym bardziej opłaca się orkiestracja; im prostszy i bardziej wymagający skali, tym bardziej choreografia.
↑ Powrót na góręKontrakty
Czym jest stamp coupling — jako wada i jako pożytek?
Stamp coupling to przekazywanie między serwisami dużej struktury danych, z której każdy korzysta tylko w małym fragmencie.
Jako antywzorzec pojawia się przy nadmiernym „specyfikowaniu na zapas". Przykład: serwis Wishlist potrzebuje z profilu tylko pola name, ale sprzęga całą strukturę Profile — wtedy zmiana zupełnie nieistotnego pola (np. state) i tak łamie kontrakt. Dochodzi problem pasma: jedna z fałszywych przesłanek systemów rozproszonych mówi, że „pasmo jest nieskończone". Tymczasem 2000 żądań/s × 500 KB payloadu to 1 GB/s; ograniczenie kontraktu do samego 200 B) zbija to do ~400 KB/s. Zasada: projektuj kontrakt na poziomie „need to know".name (
Bywa jednak pożyteczny — w zarządzaniu workflow. Gdy wymagania skali wymuszają choreografię, a workflow jest złożony, można przekazywać w kontrakcie zarówno dane domenowe, jak i stan workflow (status, stan transakcyjny). Każdy serwis aktualizuje swoją część i przekazuje dalej, a ostatni odczytuje wynik i błędy. Zwiększa to sprzężenie, ale sprzężenie semantyczne i tak musi gdzieś być — a w zamian rośnie przepustowość i skalowalność.
↑ Powrót na góręDane analityczne
Czym jest data mesh i jakie są jego cztery zasady?
Data mesh to socjotechniczne, zdecentralizowane podejście do współdzielenia i zarządzania danymi analitycznymi. Przenosi domenowe odsprzęganie znane z mikroserwisów (oraz ideę sidecara) na świat danych analitycznych: dane są własnością domen, a konsumpcja odbywa się peer-to-peer, bez pośredniczącego jeziora czy hurtowni i bez osobnego, scentralizowanego zespołu danych. Oddzielenie danych operacyjnych i analitycznych to przykład orthogonal coupling.
Cztery zasady:
- Domain ownership of data — dane są własnością domen, które znają je najlepiej (twórcy lub główni konsumenci); dystrybucja bez pośrednika.
- Data as a product — dane serwuje się jak produkt (z rolami i metrykami sukcesu), by były odkrywalne, zrozumiałe, terminowe, bezpieczne i wysokiej jakości.
- Self-serve data platform — samoobsługowe zdolności: deklaratywne tworzenie data products, wyszukiwanie po siatce, śledzenie pochodzenia (lineage).
- Computational federated governance — mimo decentralizacji wymogi compliance, bezpieczeństwa i interoperacyjności są spełniane spójnie, przez federacyjny model decyzyjny, a polityki są automatyzowane jako kod osadzony w każdym produkcie danych (architektonicznie — przez sidecar wykonujący je w punkcie dostępu).
Data mesh sprawdza się najlepiej w dojrzałych architekturach mikroserwisowych; trudny jest, gdy dane operacyjne i analityczne muszą być zawsze zsynchronizowane.
↑ Powrót na górę