Mariusz Gil, Sławomir Sobótka, Jakub Pilimon
BP ES
Dobry Big Picture Event Storming w formule as-is, musi zwracać uwagę na istotne zmiany stanu. Często, to po prostu wywołania odpowiednich endpointów. Pomniejsze kroki jak zwrócono wynik obliczenia, nie mają znaczenia.
Słowa typu zaktualizowano, zmieniono, mają ogromną tendencję do ukrywania złożoności systemu. Lepiej ich nie nadużywać 🙂
Statusy (np. w formie enumów) to tylko jeden ze sposobów implementacji stanu. Czasami znacznie lepiej użyć maszyny stanowej.
Testy integracyjne (z poziomu kontrolera) najlepiej nadają się do zabezpieczania granic. Testujemy efekty działania systemu, a nie to co zostało w nim wywołane. Następnie można refaktorować np. dodając Value Object. Potem można dodać unit testy do Value Objecta. ALE teraz będziemy mieć zduplikowane testy w pewnych miejscach. Może dojść do sytuacji, że ten sam edge case wywala testy w dwóch różnych miejscach. Wtedy warto zastanowić się, które testy lepiej wywalić. Optymalnie mieć happy patha pokrytego integracyjnymi, a resztę unitami
Make the Implicit Explicit - mówi o tym, że jeśli jakiś koncepty domenowy trzeba z kodu wywnioskować, to czytelniej będzie ten koncept jawnie zamodelować.
Value Object:
- opisuje cechy konkretnego koncpetu domenowego
- Np. Money, Distance, Tariff
- Jest niemutowalny, każda operacja na nim zwraca nowy obiekt
const moreMoney = money.add(otherMoney)
- Wiąże jeden lub wiele atrybutów w jedno
- Money = value & currency. Wszelkie operacje arytmetyczne nie maja sensu bez tego
- Poprzez enkapsulacje i stabilny interfejs łatwiej zmienić jakieś detale
- dodanie nowej właściwości, która wpływa na operacje (np. currency do
Money
), dotknie tylko jednego miejsca. Dodawanie nadal będzie wyglądać tak samo, trzeba podaćMoney
- Obiekt może zostać porównany z innymi poprzez porównanie wartości atrybutów
- jeśli coś ma currency = PLN i value = 5, i porównamy to z takim samym obiektem to mamy sensowną całość 5 zł i inne 5 zł reprezentują ten sam byt
- ^ Z tego wynika brak tożsamości
- nie ma żadnego ID, Money(5, PLN) można podmienić z innym Money(5, PLN) i nic się nie stanie.
Mieszanie się logiki biznesowej z procesową
Przykładowa sytuacja
Mamy encję CarType
i ona ma właściwości name
, description
itp. Ma też availableCarsCounter
odpowiadający za ilość dostępnych samochodów tego typu aktualnie.
Problem:
Administrator często dostaje error i nie umie zmienić opisu, bo availableCarsCouner
jest ciągle zmieniany w innej transakcji.
Rozwiązanie:
Wydzielenie availableCarsCounter
do osobnej tabeli. Wtedy transakcje będą miały odrębne granice.
^ To jest przykład sensownego rozdzielania tabeli do relacji 1:1
Naprawa bugów podczas refaktoryzacji to śliski temat. Możliwe, że gdzieś tam jest mechanizm kompensujący polegający już na istniejącym bugu, którego nie jesteśmy świadomi. Może to mieć przykre konsekwencje.
Rozdzielając kod na mniejsze kawałki, nie musimy od razu ładować tam pełnej logiki. Można to tylko owrapować nowymi metodami i zacząć propagować użycie tego interfejsu w innych częściach aplikacji. Po jakimś czasie można zamienić implementację pod spodem.
Czerwone strzałki: Ma to też zaletę, że można część logiki pakować już do nowej klasy, przed lub po wywołaniu.
Trzy rodzaje logiki:
- walidacyjna
- prosta, synchroniczna walidacja
- nie sprawdza stanu systemu
- biznesowa
- Opisuje spójną zmianę stanu
- ^ wymaga blokowania pewnych danych
- Procesowa
- opisuje przebieg długotrwałego procesu biznesowego
- JEŚLI … TO WTEDY …, A NASTĘPNIE …
- mniejsze kroki wykonywane etapami
- Może różnić się dla w zależności kto ją wykonuje
Dodawanie kolejnych przypadków biznesowych dla których kod miałby ulec zmianie jest przydatne do głębszego zrozumienia rozpatrywanego zagadnienia. Możemy dokładniej wyobrazić sobie granice lub nie zamknąć kodu na rozszerzenia. Przydatna może okazać się wizualizacja problemu - wykres, zbiory, tabele itp.
Pomocne pytania
- Kto w organizacji zajmuje się wykonaniem testowanej czynności?
- Jakie decyzje podejmuje taka osoba, a jakie działania zleca innym pracownikom?
- Jakich informacji potrzebuje taka osoba w celu podjęcia decyzji? Jakie jest źródło pochodzenia tych danych?
- Jak te informacje są utrwalane i dlaczego?
- Jakie konsekwencje powoduje wykorzystanie prez taka osobę nieaktualnych danych?
- W jaki sposób taka osoba współpracuje z innymi pracownikami? Jak się z nimi komunikuje?
Strategy pattern
Przykład: Usunięcie mil.
- Najpierw sprawdzamy, czy użytkownik ma więcej mil dostępnych niż chce mu się odjąć i czy ma konto aktywne (reguła),
- Sortujemy listę mil w zależności od czynników (dobór strategii),
- Odejmujemy mile (wykonanie).
Problemy:
- Parametry często się zmieniają (niestabilny kontrakt),
- Logika doboru strategii często się zmienia,
- Testowalność,
- Czytelność,
Rozwiązania:
Prywatna klasa do doboru strategii - nie rozwiązuje problemu z paramsami (nadal kontrakt jest niestabilny)Jeden Params do zgrupowania rzeczy potrzebnych do wyboru strategii - on sam się będzie często zmieniał, nieczytelny- Params który jest komparatorem, czyli funkcją która zostanie wywołana. Wybór musi zostać podjęty gdzieś prędzej (np. przez prywatną funkcję w serwisie, która będzie przyjmowała wszystkie parametry, albo fabrykę, która przymnie tylko to co potrzebne do wydedukowania reszty, np.
clientId
i sama sobie resztę dociągnie)
Plusy:
- Łatwość testowania za pomocą unitów.
- czytelność w samej metodzie
remove
- stabilny kod w metodzie
remove
- Łatwiej wprowadzać kolejne polityki, testy
remove
nie wybuchają
Przykładowe implementacje:
Pomocne pytania
- Czy widzisz dwa koncepcyjnie niezależne bloki kodu?
- Czy używają one rozłącznych zestawów parametrów metody?
- Czy metoda ma wiele parametrów i na dodatek często się one zmieniają?
- Czy jest blok, który nie zmienia stanu danych, tylko wybiera jakiś algorytm? Czy jest drug blok, który zmienia stan za pomocą wybranego algorytmu?
- Czy istnieje stabilna operacja? Z ewentualnym dostrojeniem?
Process level ES & Agregaty
Podczas odkrywania eventów wiele z nich może mapować się 1:1 z komendą:
- Zmień opis kierowcy ⇒ zmieniono opis kierowcy,
- Aktywuj klasę samochodu ⇒ aktywowano klasę samochodu
- Zakończ przejazd ⇒ zakończono przejazd (!)
W ostatnim przypadku to zbyt płytka analiza procesu. Owszem, wysyłamy jedną komendę “Zakończ przejazd”, ale to pociąga za sobą więcej konsekwencji np. naliczono opłatę, wystawiono fakturę.
Przydatne aspekty:
- Jakie są konsekwencje dla innych części systemu?
- Czy to już wszystkie istotne zmiany systemu podczas przetwarzania tej komendy?
- Symulacja. W jakim stanie znajduje się system przed i po zakończeniu rozkazu?
- “Wyszukano przejazd”, “Wyświetlono stan konta” - to wszystko odczyty. Nie wpływają na zmiany stanu. Warto ich unikać bo inaczej będą zaciemniać ES.
CQRS
To, że reprezentacja w bazie danych często nie odpowiada DTOsowi, to wiadomo. Mamy przecież DTO i DAO. Ale DAO może dzielić się jeszcze na 2: Model do zapisu i model do odczytu. Model do zapisu może pomijać dane które nie mają wpływu na decyzje, np. opis, nazwisko, data stworzenia. Ten model do zapisu będzie Agregatem.
Greenfield w Brownfieldzie zwiększa morale, daje pole do edukacji i pozwala na bezpieczny powrót do starej implementacji.
3 Poziomy CQRSa
Poziom 1:
- model zapisu i odczytu korzystają z tych samych danych w BD,
- Choć w kodzie możemy mieć dedykowany write model i read model
- silna spójność zapisu z odczytami,
- brak potrzeby dedykowanego zasilania danymi modelu odczytowego,
Poziom 2:
- modele zapisu i odczytu są w dwóch niezależnych, dopasowanych do swoich potrzeb strukturach,
- osobne tabelki, ale nadal ta sama BD,
- nadal może być transakcyjnie spójne
Poziom 3:
- osobne modele, nawet na poziome baz danych,
- eventual consistency,
- zasilany danymi asynchronicznie, np poprzez eventy
Query Handler Pattern
CQRS jest często mylony/mieszany z Query Handler Pattern. CQRS nie wymaga wrzucania komend na command busa.
Marketing i Komunikacja
Poniżej przedstawiono 3 modele poznawcze.
Poznawanie nowych rzeczy
Ludzie poznający nowe zagadnienia można podzielić na dwie kategorie. Będą oni przyswajać (sortować) informacje na dwa sposoby.
Przez różnicę:
- szukają przeciwieństw,
- wybierają przez odrzucanie opcji,
- zgoda przez brak różnic,
- lubią zmiany,
Przez podobieństwa:
- szukają tego co się zgadza,
- eksponują podobieństwa i analogie,
- obawiają się zmian.
IT czerpie info przez różnicę.
Biznes czerpie info przez podobieństwa.
Konflikt!
Opis rzeczywistości
Szczegółowy:
- kwantyfikatory szczegółowe i detale,
- etapy i sekwencje,
- porządek liniowy,
- często nie radzą sobie z obrazem całości,
- zastanawiają się nad całością dopiero jak mają wszystko ułożone
Ogólny:
- kwantyfikatory ogólne, generalizacje
- kolejność nie ma znaczenia,
- gubią się w szczegółach,
Trzeba zawsze się dopasować do rozmówcy. Nie trafiają do niego nowe rzeczy, bo są zbyt szczegółowe? Trzeba spróbować przenieść rozmowę na wyższy poziom.
IT ma szczegółowy obraz rzeczywistości.
Biznes ma ogólny obraz rzeczywistości.
Konflikt!
Źródło referencji i autorytetu
Najsilniejszy z 3 modeli, bo odnosi się do samoświadomości.
Wewnętrzne:
- własna perspektywa,
- poczucie, że się wie,
- potrzeba samodzielnego sprawdzenia,
- sam podejmuje decyzje,
- pochwały nie służą do potwierdzania, (nie potrzebuje opinii kogoś kto się nie zna)
- pochwały budują status,
Zewnętrzne:
- opinie innych, (bo większość ludzi, bo mówią …)
- wiedza pochodzi z zewnątrz,
- pochwała jest informacją zwrotną (coś jest dobre, coś jest złe),
Jeśli ktoś ma wewnętrzne źródło autorytetu, to argumenty typu “Eksperci X, Y, Z tak mówili na szkoleniu” nie wystarczą. I odwrotnie, jeśli ktoś ma zewnętrzne, to podsuwanie mu przykładów i eksperymentów, żeby sam się czymś pobawił/przetestował, nie będzie trafionym pomysłem.
Osoby o wewnętrznym autorytecie ciężej przekonać. Autorytety, nawet te eksperckie nie zdają się na wiele. Ale można zaproponować eksperyment. Można sterować pytaniami, Jak ty byś to rozwiązał? WDYT? “Przekładamy” na nich odpowiedzialność, to oni są teraz właścicielami problemu, to oni muszą go teraz rozwiązać.
IT jest wewnętrzne źródło referencji.
Biznes to wewnętrzne źródło referencji.
Konflikt! Każdy samemu sobie autorytetem.
Nastawienie do świata
Od problemu:
- filtrowanie głównie problemów i zagrożeń,
- wiedzą czego nie robić (działa? To nie dotykaj),
- brak problemu oznacza, że jest dobrze
- problemy rozpraszają w dążeniu do celu
Do celu:
- filtrowanie głownie celów i korzyści
- Zawsze może być lepiej
- nie widzą problemów w dążeniu do celu
Tłumaczenie osobie nastawionej na problemy, że wszystko będzie dobrze nie ma sensu. Trzeba zapewnić jakąś poduszkę bezpieczeństwa. Warto rozpoznać źródło autorytetu takiej osoby. Charakteryzuje IT.
Z koli wytykanie błędów osobie nastawionej na cel, będzie odbierane jako podcinanie skrzydeł. Jeśli już musimy zwrócić na coś uwagę, to warto połączyć to z zagrożeniem jakiegoś celu. Charakteryzuje biznes.
Motywowanie siebie
reaktywne:
- czekanie na bodziec z zewnątrz,
- oczekiwanie na odpowiednią sytuacje,
- czasem bezradność i paraliż analityczny,
proaktywne:
- sami inicjują działania,
- nie czekanie na innych,
- możliwe zbyt pochopne działania,
Dla osoby reaktywnej trzeba znaleźć trigger do działania. Charakteryzuje IT
Osobę proaktywną można skonfrontować z celami. Czy jest sens poświęcać ten czas na to? Charakteryzuje Biznes
Osobie nastawionej na możliwości, często nie można pokazać gotowego rozwiązania. Trzeba dać opcję. I to nie mogą być 2 opcje tak lub tak (wtedy to dylemat). Najlepiej pokazać 3+ możliwość, od najmniej pożądanej do najbardziej pożądanej.
Pierwszeństwo
O kim myślę w pierwszej kolejności? O kogo dbam? Na kogo kieruję korzyści?
ja:
- moje odczucia i doświadczenia,
- nie zwracają uwagi na mowę ciała innych,
- można podjąć dwie strategie podczas przekonywania takiej osoby:
- przyjazna - jakie ty korzyści możesz wyciągnąć z tego?
- ofensywna - projekt nie kręci się wokół ciebie
inni
- reakcje i sygnały od innych
- ocena przez pryzmat innych
- przekonywać można dbając/pomagając o tę osobę:
- jeśli nie odpoczniesz, nie będziesz mógł pomagać innym
Lider je ostatni. Dobry lider techniczny powinien najpierw dbać o swoich.
IT to “ja”
Biznes to “inni”
nauka modeli mentalnych
Ćwiczyć świadome korzystanie z tych modeli mentalnych jest cholernie trudne bo po pierwsze jest to bardzo wymagające intelektualnie, a po drugie - jeśli będziemy próbowali to robić w całość i w locie, to wyjdzie to nienaturalnie, wręcz komicznie.
Dlatego warto sobie to poćwiczyć np. w mailach/wiadomościach na Slacku. Przed wysłaniem jakiejś dłuższej wiadomości można na chłodno przeanalizować swoją. Wykryć i nazwać modele mentalne. Następnie spróbować przeanalizować drugą stronę. Na końcu dopasować przekaz do tej drugiej strony. Dopasowanie należy zawsze do osoby bardziej świadomej.
Poznawanie modeli mentalnych i stosowanie ich, pozwala na stosowanie ich. A wchodzenie w takie różne modele mentalne daje wybór. Pomaga zrozumieć problemy i innych z odmiennej perspektywy.
Model rozwoju kompetencji braci Dreyfus
5 poziomów:
- Nowicjusz
- konkretne kroki (gdzie mam wstawić tego
if
a? ) - bez zainteresowania szerszym kontekstem,
- brak zdolności myślenia na poziomie konceptualnym,
- Zaawansowany początkujący
- tendencja do zawyżania kompetencji (szczyt głupoty w modelu Duninga-Kruggera),
- potrzeba szybkich odpowiedzi,
- formułowanie pierwszych reguł i próba podważania ich,
- czuje, że coś jest źle, jak już naprawdę nie działa/boli, ale nie wie jak to naprawić
- Kompetentny
- zdolność operowania modelami
- samodzielne stawianie celów (a nie reagowanie),
- planowanie konkretnych kroków, zamiast bycia prowadzonym
Do tego momentu może dojść każdy, liniowo. Przejście dalej nie jest kwestią czasu. Potrzeba tu świadomej pracy.
- Biegły
- podważamy wzorce i reguły, ponieważ nas ograniczają,
- potrzeba big picture,
- potrzeba głębszego zrozumienia przyczyn (dlaczego ten framework, dlaczego ta BD)
- sfrustrowani uproszczeniami - potrzeba głębszego rozumienia przyczyn,
- Ekspert
- intuicja (wiedza, której nie da się zwerbalizować)
- rozwiązania się “pojawiają”
- operowanie metaforami,
- kontekst to podstawa (wywody bez kontekstu są pozbawione sensu)
^ Jest to dobre do przekazywania wiedzy. Z nowicjuszem (juniorem) nie będziemy rozmawiali na poziomie wzorców i myśleniu o big picture. A z biegłym o konkretnych wystąpieniach ifków w kodzie. Trzeba dopasować poziom przekazu do odbiorcy
Cykl kolba
To nie ma startu, można zacząć od doświadczenia.
Żeby ludzie zbytnio nie przywiązywali się do swoich rozwiązań, można powiedzieć “Wskażcie przynajmniej 2 rozwiązania”.
Aby pobudzić kreatywność można powiedzieć “wskażcie 2 działające, ale błędne rozwiązania”.
Jeśli osoba lubi unikać problemów, to można zacząć od pokazywania tych problemów, najlepiej jak ta osoba same je zauważy.
“To nie ma sensu. Reguł jest 100k, każdy będzie sobie je dawał gdzie chce…”. Warto wyłapywać uogólnienia, następnie rozbijać je (mamy 3 rodzaje reguł …).
Change Coupling - coupling określający co się zawsze razem musi zmienić. Np. mając typowe 3 warstwy, praktycznie zawsze walniemy wertykalną zmianę
Wzorce analityczne
Party
Przydaje się jak chcemy w bardzo elastyczny sposób wyrazić relację
- Parties
PartyID
(Primary Key)PartyType
(e.g., 'Individual', 'Organization')Name
- Other attributes like
ContactInformation
,DateOfBirth
for individuals,RegistrationNumber
for organizations, etc. - Roles
RoleID
(Primary Key)RoleName
(e.g., 'Customer', 'Supplier', 'Employee')- PartyRoles
PartyRoleID
(Primary Key)PartyID
(Foreign Key to Parties)RoleID
(Foreign Key to Roles)- Date or context-related attributes (like
StartDate
,EndDate
) - Relationships
RelationshipID
(Primary Key)FromPartyRoleID
(Foreign Key to PartyRoles)ToPartyRoleID
(Foreign Key to PartyRoles)- Relationship-specific attributes (like
RelationshipType
,StartDate
,EndDate
)
^ można to też uprościć, jeśli PartyRoleType
będzie enumem (czyli nie będziemy potrzebować tam więcej informacji) i/lub jeśli PartyRelationshipType
będzie enumem, to wtedy wystarczy nam środkowa kolumna z tego obrazka, bo w tabeli PartyRole
dodamy sobie kolumnę
typu enum i/lub w PartRelationShip
też tak idzie zrobić
Cały mechanizm party nie powinien znajdować się w osobnym microserwisie, wtedy mamy single point of failure. To może zostać biblioteką która będzie mogła zostać zaimportowana do Modułu (Bounded Contextu). To że coś ma taką samo strukturę danych nie świadczy o tym, że to będzie ten sam serwis!
Jednym z lepszych sposobów na destylację kontekstów, jest zadawanie pytań. Kto może to zrobić? Jak może to zrobić? Jakie reguły muszą być spełnione aby to było możliwe?
Lokalna Struktura kodu
Object-Orientation Abusers - kiedy klasa coś rozszerza, a w rzeczywistości rzuca wyjątkiem. Albo jak implementuje jakieś zachowanie (doJob
), a środku jest kaskada ifów/swtichów po Class.type żeby odpalić odpowiednie zachowanie.