Pięć książek, jeden system: architektura od decyzji do niezawodnej produkcji
Dwie rezerwacje na jeden termin, trzy definicje statusu i zielony pipeline przed awarią. Na przykładzie firmy serwisowej łączymy pięć ważnych książek o architekturze z decyzjami w kodzie, DDD, outbox i SLO. Z animowanym przepływem, ćwiczeniem kolejności i quizem.
Poniżej znajduje się pełna treść artykułu. Quizy i symulacje wymagają włączenia JavaScript.
ARCCLOUD / PRACOWNIA ARCHITEKTA / 02
W czwartek wszystko działało. W piątek dwie osoby dostały ten sam termin.
Zespół dopisał integrację z magazynem, poprawił formularz i wypuścił wersję. Pipeline był zielony. Rano konsultant przyjął dwa zlecenia na tę samą ekipę. Jedno z nich miało już zarezerwowaną część, drugie zdążyło wysłać potwierdzenie klientowi. Każdy fragment kodu robił coś rozsądnego. Cały proces przestał się zgadzać.
W takich chwilach architektura przestaje być rysunkiem z prostokątami. Staje się odpowiedzią na konkretne pytania: kto może potwierdzić termin, czyja jest rezerwacja i co obiecaliśmy człowiekowi po drugiej stronie ekranu?
Wybrałem pięć książek, które pomagają przejść przez te pytania w dobrej kolejności. Zaczniemy od wymagań, zajrzymy do kodu, uporządkujemy istniejący system, a na końcu sprawdzimy, czy potrafimy utrzymać go po wdrożeniu. Każda książka wnosi inny sposób patrzenia. Łączy je odpowiedzialność za konsekwencje własnych decyzji.
Wymagania · Wzorce w kodzie · Refaktoryzacja · Granice i zdarzenia · Niezawodność · Sprawdź decyzje
01 / FUNDAMENTALS OF SOFTWARE ARCHITECTURE
Dobry projekt zaczyna się od problemu, który potrafimy nazwać.
Mark Richards i Neal Ford porządkują style architektoniczne oraz cechy jakościowe systemu. Ich punkt wyjścia jest prosty: decyzja architektoniczna ma cenę. Wybierając większą niezależność wdrożeń, możemy zapłacić trudniejszym śledzeniem błędów. Upraszczając transakcję, możemy związać część zmian jednym wdrożeniem. Książkę warto czytać jako trening uzasadniania wyborów. Opis drugiego wydania u wydawcy.
W Serwisie Północ trzy osoby rozwijają jedną aplikację. Najwięcej zgłoszeń dotyczy terminów i rezerwacji części. To wystarczający powód, żeby zacząć od reguł procesu. Sama informacja, że firma chce „nowoczesnej architektury”, nie mówi jeszcze, jakie rozwiązanie jej pomoże.
| Ogólna prośba | Sprawdzalna wersja w naszym ćwiczeniu | Test, który ujawni problem |
|---|---|---|
| Ogólna prośbaSystem ma być niezawodny. | Sprawdzalna wersja w naszym ćwiczeniuKażde przyjęte zlecenie otrzymuje trwały identyfikator i możliwy do odczytania status. | Test, który ujawni problemPrzerwij połączenie po zapisie i ponów identyczne żądanie. |
| Ogólna prośbaTerminy mają się zgadzać. | Sprawdzalna wersja w naszym ćwiczeniuJedna ekipa nie obsługuje dwóch nakładających się, potwierdzonych wizyt. | Test, który ujawni problemDwa równoległe żądania próbują zająć ostatni wolny termin. |
| Ogólna prośbaDane mają być bezpieczne. | Sprawdzalna wersja w naszym ćwiczeniuPracownik firmy A nie odczyta zlecenia firmy B przez API, eksport ani odgadnięty identyfikator. | Test, który ujawni problemPowtórz te same operacje z tożsamością innej firmy. |
| Ogólna prośbaZmiany mają być łatwe. | Sprawdzalna wersja w naszym ćwiczeniuNowy sposób wyceny nie wymaga poprawiania harmonogramu i kodu wysyłającego wiadomości. | Test, który ujawni problemDodaj wariant wyceny i sprawdź, jakie moduły trzeba zmienić. |
Dopiero teraz rozmowa o stylu architektury nabiera sensu. Dla tej firmy wybieramy na początek modularny monolit: jedno wdrożenie, ale jawne granice odpowiedzialności. Zyskujemy prostsze uruchomienie i możliwość użycia lokalnych transakcji. Przyjmujemy też koszt wspólnego wydania i potrzebę pilnowania zależności pomiędzy modułami.
To wybór dla opisanych warunków. Gdy harmonogram zacznie rozwijać osobny zespół albo jego obciążenie będzie wyraźnie inne od reszty aplikacji, wrócimy do tej decyzji. Dobry dokument architektoniczny zawiera również sygnał, po którym trzeba go ponownie przeczytać.
Przykład krótkiego ADR: dlaczego zaczynamy od modularnego monolitu?
Kontekst: trzy osoby, jeden produkt, częste zmiany reguł rezerwacji, brak osobnego dyżuru dla każdej usługi.
Decyzja: moduły Zlecenia, Harmonogram, Magazyn i Rozliczenia we wspólnej aplikacji. Każdy udostępnia kontrakt i jest właścicielem swoich zapisów.
Konsekwencje: prostsze wdrożenie i diagnostyka, ale wspólne skalowanie procesu oraz ryzyko przenikania zależności. Testy architektury mają wykrywać odwołania do wnętrza obcego modułu.
Powrót do decyzji: gdy pojawi się osobny zespół, udokumentowana potrzeba niezależnego skalowania lub problem izolacji awarii, którego nie rozwiązaliśmy w obecnym układzie.
02 / PATTERNS OF ENTERPRISE APPLICATION ARCHITECTURE
Wzorzec pomaga dopiero wtedy, gdy pasuje do skomplikowania reguł.
Martin Fowler, wraz ze współautorami, opisuje powtarzające się problemy aplikacji biznesowych: organizację logiki, zapis danych czy współbieżną zmianę informacji. Starsza data publikacji nie odbiera wartości pytaniom, które stawia. W praktyce przydaje się odróżnienie prostego Transaction Script od bogatszego Domain Model oraz rozumienie granicy Unit of Work. Strona książki autora.
Załóżmy, że administracja zmienia opis stanowiska serwisowego. Walidacja uprawnień, sprawdzenie długości, zapis — prosty przypadek może pozostać prostym skryptem transakcyjnym. Nie musimy budować wokół niego rozbudowanego modelu.
Potwierdzenie naprawy jest trudniejsze. Potrzebujemy zatwierdzonej wyceny, części i ekipy. Jeżeli te warunki są sprawdzane osobno w kontrolerze, zadaniu nocnym i integracji, po kilku miesiącach dostaniemy trzy różne definicje „potwierdzonego zlecenia”. Warto zebrać regułę w jednym modelu.
public enum WorkOrderState { Draft, Confirmed }
public sealed record ConfirmationEvidence(
bool QuoteAccepted,
bool PartsReserved,
bool TechnicianReserved);
public sealed class WorkOrder
{
public WorkOrderState State { get; private set; }
public void Confirm(ConfirmationEvidence evidence)
{
if (State == WorkOrderState.Confirmed) return;
if (!evidence.QuoteAccepted || !evidence.PartsReserved ||
!evidence.TechnicianReserved)
throw new InvalidOperationException("Confirmation is incomplete.");
State = WorkOrderState.Confirmed;
}
}
Ten fragment pokazuje jedną regułę, nie gotowy proces produkcyjny. W działającej aplikacji dowód rezerwacji musi pochodzić z zaufanego kontraktu i odnosić się do właściwego zlecenia oraz wersji. Trzeba również obsłużyć wygaśnięcie rezerwacji, uprawnienia i równoległe zapisy. Trzy wartości logiczne przesłane z przeglądarki nie mogą stanowić takiego dowodu.
W teście jednostkowym potwierdzamy, że model odrzuca niepełne warunki. W teście integracyjnym uruchamiamy dwa równoległe zapisy i sprawdzamy mechanizm współbieżności w bazie. To dwa różne rodzaje pewności. Test samej klasy nie wykaże podwójnej rezerwacji wykonanej przez dwa procesy.
Unit of Work pomaga zebrać zmiany należące do jednej operacji zapisu. Nie rozciąga automatycznie transakcji na zewnętrzny magazyn, broker i serwer poczty. Tę granicę dobrze jest narysować, zanim kod zacznie ją nieświadomie przekraczać. Krótkie opisy wzorców: Transaction Script, Domain Model i Unit of Work.
03 / DOMAIN-DRIVEN REFACTORING
Najpierw rozplącz znaczenia. Potem rozdziel kod.
Alessandro Colla i Alberto Acerbis pokazują zastosowanie DDD do systemu, który już istnieje: od języka domeny i kontekstów po zmianę struktury oraz bazy danych. Ważny jest sam kierunek pracy — poprawianie granic stopniowo, przy zachowaniu działającego produktu. Modularny system może być dobrym wynikiem refaktoryzacji; wydzielenie mikroserwisu wymaga własnego uzasadnienia. Książka u wydawcy.
W naszej firmie słowo „gotowe” znaczy co innego dla trzech osób. Konsultant ma na myśli uzgodnioną cenę, magazynier — odłożoną część, a technik — wykonaną naprawę. Jedna kolumna Status nie usuwa tej różnicy. Ukrywa ją do pierwszego nietypowego przypadku.
Cel naprawy, zgoda klienta i cykl życia zlecenia.
Właściciel: obsługa procesu.Dostępność ekipy, rezerwacja i zwolnienie terminu.
Właściciel: planowanie pracy.Stan części i ważność rezerwacji materiału.
Właściciel: gospodarka częściami.Dokumenty, należności i wynik rozliczenia.
Właściciel: finanse.Granice zaczynamy ustalać przy rozmowie z ludźmi, którzy wykonują pracę. Pytamy o konkretną reklamację: co zaszło, kto podjął decyzję i jakiego faktu wtedy brakowało. Przydatny model wychodzi z takich rozmów. Nazwy folderów powinny dopiero go odzwierciedlać.
Jak ruszyć stary system bez wielkiego przepisywania?
- Wybierz jeden bolesny scenariusz. Na przykład potwierdzanie terminu. Zapisz regułę z osobą odpowiedzialną za proces.
- Zabezpiecz potrzebne zachowanie testami. Ustal, które obecne wyniki są poprawne. Utrwalenie starego błędu w teście nie robi z niego wymagania.
- Wprowadź jedną ścieżkę dostępu. Kod zleceń prosi harmonogram o rezerwację przez kontrakt. Nie zmienia jego tabel bezpośrednio.
- Przenieś odpowiedzialność za zapis. Ustal moment przełączenia i jednego właściciela. Dwa niezależne mechanizmy zapisujące te same dane utrudnią wykrycie rozbieżności.
- Porównaj wyniki i usuń starą drogę. Sprawdź dane historyczne, przypadki brzegowe i plan powrotu. Dopiero potem sprzątaj zbędne zależności.
Wspólna baza może pozostać na tym etapie. Właściciel danych oznacza odpowiedzialność za ich zmianę, reguły i kontrakt, a nie automatycznie osobny serwer bazodanowy. W modularnym monolicie można zacząć od schematów, widoczności typów i testów zależności. Tam, gdzie potrzebujemy mocniejszej izolacji, dokładamy odpowiednie uprawnienia oraz granice wykonania.
04 / SOFTWARE ARCHITECTURE: THE HARD PARTS
Kiedy moduł staje się usługą, do rozmowy dołącza sieć.
Neal Ford, Mark Richards, Pramod Sadalage i Zhamak Dehghani analizują trudne decyzje dotyczące rozmiaru usług, danych, kontraktów i rozproszonych procesów. Warto sięgnąć po tę książkę, kiedy kilka rozsądnych rozwiązań ma różne, istotne koszty. Uczy porównywania kompromisów w kontekście konkretnego systemu. Opis książki u wydawcy.
Przypuśćmy, że harmonogram ma już własny zespół i integruje się z kilkoma partnerami. Osobna usługa może dać temu zespołowi swobodę wdrażania. Jednocześnie zwykłe wywołanie metody zamienia się w operację, która może się opóźnić, wykonać dwukrotnie lub zakończyć bez odpowiedzi. Biznesowa reguła nadal obowiązuje, ale zmieniły się warunki jej obrony.
| Pytanie | Moduł we wspólnym procesie | Osobna usługa |
|---|---|---|
| PytanieWdrożenie | Moduł we wspólnym procesieWspólne wydanie wymaga koordynacji. | Osobna usługaNiezależność wymaga kompatybilnych kontraktów i własnego procesu wydania. |
| PytanieDane | Moduł we wspólnym procesieLokalna transakcja może objąć współpracujące części. | Osobna usługaWiele baz zwykle oznacza osobne transakcje i jawne stany pośrednie. |
| PytanieAwaria | Moduł we wspólnym procesieWspółdzielone zasoby mogą przenieść przeciążenie dalej. | Osobna usługaIzolację trzeba zbudować: timeout, limit kolejki, kontrola ponowień i obserwowalność. |
| PytanieDiagnostyka | Moduł we wspólnym procesieŁatwiej odtworzyć jeden stos wykonania. | Osobna usługaPotrzebujemy korelacji żądań, zdarzeń i zmian stanu. |
Outbox zamyka konkretną lukę. Cały proces wymaga jeszcze kilku decyzji.
Jeżeli zapiszemy zlecenie, a potem wyślemy zdarzenie, proces może zakończyć się pomiędzy tymi operacjami. Rekord zostanie, wiadomość nie dotrze. Wzorzec transactional outbox zapisuje zmianę biznesową i zamiar publikacji w tej samej lokalnej transakcji. Osobny mechanizm publikuje później zdarzenie. Broker albo publisher nadal może dostarczyć duplikat, dlatego odbiorca musi być na to przygotowany. Opis outbox w Azure Architecture Center.
ANIMOWANA MAPA / URUCHAMIASZ JĄ SAMODZIELNIE
Jedno zlecenie, kilka odpowiedzialności
Pokaż przepływ krok po kroku
To krótka animacja objaśniająca kolejność. Nie łączy się z brokerem i nie wykonuje audytu infrastruktury.
W odbiorcy zapis identyfikatora obsłużonego zdarzenia i zmiana biznesowa powinny tworzyć jedną lokalną transakcję, z ograniczeniem unikalności dla pary odbiorca–EventId. Samo sprawdzenie „czy już widziałem?” przed zapisem nie wystarcza przy równoległych wiadomościach. Jeśli efektem ma być wywołanie zewnętrznego API, jego bezpieczeństwo ponowień wymaga osobnego rozwiązania, na przykład klucza idempotencji obsługiwanego przez dostawcę.
Potrzebujemy też polityki kolejności. Zdarzenie z wersją 8 nie powinno po cichu cofnąć projekcji, która zna wersję 9. Możemy kontrolować wersje i obsługiwać luki albo użyć mechanizmu uporządkowania dla danego zlecenia. Wybór zależy od brokera, sposobu przetwarzania i tego, czy wymagamy kolejności dla jednego obiektu, czy szerszej grupy. Globalne porządkowanie wszystkiego często kosztuje więcej, niż daje.
Saga i kompensacja: co naprawdę potrafimy odwrócić?
Po wydzieleniu magazynu i harmonogramu nie mamy jednej transakcji obejmującej cały proces. Możemy koordynować lokalne kroki sagą. Gdy rezerwacja ekipy się nie powiedzie, uruchamiamy uzgodnione działania naprawcze: zwalniamy część, zapisujemy wynik i informujemy obsługę. Kompensacja sama może się nie udać, więc potrzebuje trwałego stanu, ponowień i ścieżki ręcznej interwencji. Wzorzec Saga w dokumentacji Microsoft.
Nie cofniemy czasu ani przeczytanej wiadomości. „Anulowano” musi mieć znaczenie ustalone z firmą. Jeśli stan zewnętrznej rezerwacji jest nieznany po timeout, nie oznaczamy jej od razu jako nieudanej. Najpierw uzgadniamy wynik. Interfejs powinien pokazać uczciwy stan oczekiwania lub potrzebę weryfikacji, zamiast obiecywać zakończenie.
05 / SITE RELIABILITY ENGINEERING
Architektura musi jeszcze pomóc człowiekowi, który odbierze telefon po awarii.
Książka pod redakcją Betsy Beyer, Chrisa Jonesa, Jennifer Petoff i Nialla Richarda Murphy’ego pokazuje, jak połączyć rozwój produktu z odpowiedzialnością za jego działanie. Przydatnym początkiem są SLI, SLO oraz budżet błędów: co mierzymy, jaki poziom uznajemy za wystarczający i jak reagujemy, gdy go tracimy. Oficjalna, bezpłatna wersja książki.
Dla Serwisu Północ ustalamy przykładowy cel: w ruchomym oknie 28 dni co najmniej 99,5% poprawnie przyjętych zleceń otrzymuje prawidłowy, trwały wynik w ciągu 120 sekund. Wynikiem może być potwierdzenie albo uzasadnione odrzucenie. Błąd infrastruktury nazwany „odrzuceniem” nie staje się poprawnym wynikiem. Definicję uzgadniamy przed pomiarem.
To budżet liczby operacji, nie minut przestoju. Ponowienia tej samej operacji nie powinny zwiększać mianownika. Musimy też ustalić, jak traktujemy operacje przyjęte tuż przed końcem okresu obserwacji, które jeszcze nie miały pełnych 120 sekund na zakończenie. Bez takiej definicji dwa zespoły mogą uczciwie wyliczyć różne wyniki. SLI i SLO według Google SRE.
Sam zielony endpoint zdrowia nie potwierdzi tego celu. Potrzebujemy zdarzeń początku i końca procesu oraz zgodnych identyfikatorów. Pomocniczo obserwujemy wiek najstarszej wiadomości w outbox, opóźnienie odbiorcy, liczbę stanów wymagających weryfikacji i nieskuteczne kompensacje. Te sygnały mają pomagać odnaleźć przyczynę, a nie zastępować pomiar doświadczenia użytkownika.
Z budżetem wiążemy wcześniej uzgodnioną reakcję. Jeśli tempo błędów zapowiada szybkie wyczerpanie budżetu, zespół ogranicza ryzykowne wydania, szuka przyczyny i poprawia niezawodność. Awaryjne poprawki oraz zmiany bezpieczeństwa mają osobną ocenę ryzyka. Konkretna polityka należy do produktu i jego zespołu; można zacząć od przykładowej polityki z The Site Reliability Workbook.
Co powinno czekać obok kodu?
- Mapa własności. Kto prowadzi incydent i kto zna każdy fragment procesu.
- Instrukcja działania. Jak odnaleźć zlecenie po identyfikatorze, rozpoznać bezpieczne ponowienie i skierować sprawę do weryfikacji.
- Plan odtwarzania. Kopia bazy oraz przećwiczona procedura przywrócenia i uzgodnienia stanu integracji. Sam status „backup wykonany” nie jest testem odtworzenia.
- Warunki wdrożenia. Zgodność kontraktów i migracji, kontrolowane uruchomienie oraz mierzalny warunek zatrzymania wydania.
- Wnioski po incydencie. Co utrudniło diagnozę, jaka ochrona zawiodła i jak sprawdzimy skuteczność poprawki.
Powrót do poprzedniej wersji aplikacji nie odwraca sam z siebie zmian danych ani skutków w zewnętrznym systemie. Plan awaryjny trzeba więc sprawdzić na scenariuszu z prawdziwą kolejnością operacji, a nie wyłącznie na pustej bazie testowej.
WARSZTAT / OD ROZUMIENIA DO DECYZJI
Ułóż przepływ. Potem obroń pięć decyzji.
W pierwszym ćwiczeniu wybierz kroki w poprawnej kolejności. Zakładamy jeden lokalny zapis zlecenia z outbox oraz osobnego odbiorcę. Wiadomości mogą zostać dostarczone ponownie. Wynik ćwiczenia pozostaje w tej karcie przeglądarki.
0/5 kroków ułożonych
Publikuj wiadomość ze stałym EventId
Zapisz zlecenie i outbox w jednej transakcji
Potwierdź odbiór wiadomości: ACK
Wykonaj COMMIT
Zapisz efekt i unikalny wpis inbox w transakcji odbiorcy
Zacznij od przygotowania trwałej zmiany w lokalnej transakcji.
Ułóż od nowa
Pięć sytuacji z codziennej pracy
0/51. Trzy osoby, jeden produkt, bolesne rezerwacje. Od czego zaczynasz?
Od wydzielenia wszystkich tabel do mikroserwisów.
Od reguł biznesowych, scenariuszy jakościowych i kosztów rozwiązań.
Wybierz odpowiedź, aby zobaczyć uzasadnienie.
2. Ta sama reguła potwierdzenia zlecenia istnieje w trzech integracjach.
Zbierasz regułę w modelu i weryfikujesz pozostałe ścieżki zapisu.
Zmieniasz nazwy klas, pozostawiając trzy implementacje.
Wybierz odpowiedź, aby zobaczyć uzasadnienie.
3. Wprowadzacie moduły do starszej aplikacji.
Nowy i stary moduł niezależnie zapisują te same rekordy bez uzgadniania.
Przenosicie jeden scenariusz przez kontrakt i ustalacie właściciela danych.
Wybierz odpowiedź, aby zobaczyć uzasadnienie.
4. Publisher wysłał wiadomość, ale zakończył pracę przed oznaczeniem jej jako wysłanej.
Liczysz się z duplikatem i chronisz efekt u odbiorcy.
Zakładasz, że outbox eliminuje wszystkie duplikaty.
Wybierz odpowiedź, aby zobaczyć uzasadnienie.
5. Przy 20 000 kwalifikujących się operacji cel 99,5% daje jaki budżet?
100 minut przestoju.
100 nieprawidłowych lub spóźnionych wyników.
Wybierz odpowiedź, aby zobaczyć uzasadnienie.
Wybierz odpowiedź w każdej sytuacji. Możesz wrócić i ją poprawić.
Rozwiąż ponownie
Po lekturze wybierz jedną decyzję, którą poprawisz w swoim systemie.
Nie musisz wdrażać wszystkich wzorców naraz. Opisz jeden proces: co przyjmuje, gdzie zapisuje prawdę, jak reaguje na powtórzenie i po czym rozpoznajesz jego zakończenie. Zaznacz miejsca, w których odpowiedź jest dziś zgadywana. Z takiej kartki często wychodzi lepszy plan pracy niż z listy nowych technologii.
Kolejność czytania: Fundamentals pomoże nazwać wymagania i kompromisy. U Fowlera sprawdzisz wzorce pasujące do logiki. Domain-Driven Refactoring przyda się przy porządkowaniu istniejącego kodu, The Hard Parts przy rozdzielaniu usług i danych, a SRE przy zamianie obietnic w pomiar oraz codzienną praktykę. Wracaj do odpowiedniego rozdziału, kiedy pojawi się konkretna decyzja.
Chcesz przełożyć te pytania na swoją aplikację?
W ArcCloud sprawdzamy kod, integracje, dane i sposób wdrażania, a ustalenia zamieniamy w plan napraw z priorytetami. W uzgodnionym zakresie opisujemy problem, jego wpływ, proponowaną zmianę oraz sposób sprawdzenia efektu. Po zaakceptowaniu zakresu możemy również wykonać poprawki i pokazać wynik testów.
Zobacz zakresy audytów i zgłoś system →Porozmawiaj o problemie w projekcie →Książki i źródła do dalszej pracy
To autorski poradnik oparty na zagadnieniach poruszanych w wymienionych publikacjach oraz dokumentacji. Linki prowadzą do autorów, wydawców i oficjalnych materiałów. Ćwiczenia nie wymagają zakupu książek.
- Fundamentals of Software Architecture, 2nd Edition — Mark Richards, Neal Ford.
- Patterns of Enterprise Application Architecture — Martin Fowler, ze współautorami wskazanymi na stronie książki.
- Domain-Driven Refactoring — Alessandro Colla, Alberto Acerbis.
- Software Architecture: The Hard Parts — Neal Ford, Mark Richards, Pramod Sadalage, Zhamak Dehghani.
- Site Reliability Engineering — redakcja: Betsy Beyer, Chris Jones, Jennifer Petoff, Niall Richard Murphy.
Dodatkowo: Saga, Transactional Outbox, Service Level Objectives oraz Error Budget Policy.