poniedziałek, 10 listopada 2025

Domain Model cz4

 Kolejny raz bez kodu, kolejny raz z Cluade-m w roli analityka.

Głównym tematem dziś będą Pivotal Events i bounded contexty.

Na sesji Event Stormingu odkryliśmy trzy tzw. pivotal events. 

Czym jest pivotal event, to nie będzie definicja, bo jej nie znam, wiem tylko tyle, że takie zdarzenie określa, że system wchodzi w nowy kontekst, nowy stan, który jest nieodwracalny. Takie zdarzenie wyznacza granicę kontekstu. Zmienia słownictwo.

Wyjaśnię to zaraz na przykładzie pierwszego kontekstu i pierwszego takiego zdarzenia(pivotal event).

Poruszam się w kontekście maratonu rowerowego.

Pierwsze zdarzenie o którym mowa to 

Przypisano numer startowy

Te zdarzenie zamyka kontekst rejestracji zawodnika. 

W obrębie tego kontekstu każda osoba, która zgłosi chęć uczestnictwa w maratonie staje się jego uczestnikiem, czyli zostaje zarejestrowana w tym wydarzeniu. Oznacza to, że może wybrać dystans na którym pojedzie, musi podać wszystkie dane, które są obowiązkowe, może je dowolnie zmieniać, również dystans.

Jednak w chwili opłacenia startu, zostaje przypisany uczestnikowi numer startowy. Skutkiem czego uczestnik staje się zawodnikiem, czyli godzi się na warunki startu, wyraża chęć uczestnictwa poprzez wniesienie opłaty. W tym momencie zawodnik traci możliwość zmiany dystansu i zmiany danych, które wprowadził. Zyskuje jednak dostęp do nowego kontekstu, kontekstu związanego z generowaniem grup startowych.

Te zdarzenie jest nieodwracalne nie da się przełączyć zawodnika z powrotem na uczestnika. Zawodnik może zrezygnować ze startu,  rezygnacja zawodnika wiąże się z wykreśleniem go z puli zawodników, ale jego numer nie trafia już do wolnych numerów. Jeżeli zawodnik, który zrezygnował postanowi jednak się ponownie zarejestrować i opłacić udział otrzyma nowy numer.

Tak więc te zdarzenie jest pivotalne i wyznacza granicę kontekstu rejestracji.

Kolejne zdarzenie, które wyznacza granicę kontekstu to

Wystartowano zawodników

To zdarzenie kończy kontekst generowania i przygotowania zawodników - grup do startowych. Po tym zdarzeniu zawodnik może już tylko jechać do przodu, nie da się cofnąć startu. Zawodnik może zrezygnować ale wiąże się to z definitywnym zakończeniem zawodów i dyskwalifikacją. Zawodnik może nie ukończyć swojego dystansu, np jedzie na 400 ale przejedzie tylko 100, będzie inaczej traktowany niż ci, którzy jadą 100 km ale reguły tego traktowania są zapisane w tym nowym kontekście, który zaczyna się od zdarzenia Wystartowano zawodników.

Zastanawiałem się nad słownictwem w tym kontekście. W poprzednim uczestnik zmieniał się w zawodnika, tu nie następuje tak spektakularna zmiana. Mamy dalej zawodników ale np grupa nie ma już znaczenie żadnego, sam zawodnik jako osoba, też nie bardziej liczy się czas przejazdu i numer, dystans również. Zawodnik w tym kontekście otrzymuje nowe cechy, które go definiują czyli wspomniany wyżej czas przejazdu i pokonana odległość czyli dystans. Tyle że wcześniej ten dystans to był założeniem deklaracją teraz jest realizacją.

Kolejne ważne zdarzenia, to 

Zakończono przejazdy

Te zdarzenie kończy maraton, jest zamknięciem pomiaru czasu. Jest granicą kontekstu ponieważ po tym zdarzeniu pomiar czasu jest nie możliwy, a wszystkie zarejestrowane czasy stają się historią danej edycji maratonu. Tu należy nadmienić, że jest to maraton 24h. Każdy dystans ma swój limit czasowy, dla przykładu 600 km ma 24h, 300 km 12h kolejne dystanse krócej. Dystanse nie startowały razem, tzn. były wydzielone te, które stratowały w sobotę (maraton był z soboty na niedziele). W sobotę startowały najdłuższe dystanse czyli te, które miały ograniczenie do 24h, kolejne krótsze startowały w niedziele ale tak żeby wszyscy mogli kończyć jednocześnie.

Dzięki temu można było zakończyć maraton dla wszystkich o tej samej porze, o ustalonej godzinie. Czyli zdarzenie zakończono przejazdy też można wrzucić do worka zdarzeń pivotalnych, czyli jak się wydarzy nie można go cofnąć, jest granicą poprzedniego kontekstu, otwiera nowy w którym zawodnik jest rekordem w tabeli wyników a pod uwagę brany jest jego dystans i czas przejazdu i czy przejechał deklarowaną odległość.

Typy zdarzeń 

Claude zatrudniłem jako pomoc w obaleniu tych zdarzeń, to była bitwa na argumenty. Te trzy zdarzenia się obroniły ale okazało się, że mają nie co inny zasięg.

  • przypisano numer startowy,
  • wystartowano zawodników
Te dwa zdarzenia mają charakter bardziej lokalny, szczególnie pierwsze gdzie zmieniamy słownictwo, uczestnik zmienia się na zawodnika. Jest to bardziej widoczne niż w drugim kontekście gdzie dochodzi nam aspekt czasu. Ale te dwa zdarzenia mają charakter lokalny bo nie odnoszą się do wszystkich, zawodników, do całego systemu. Ich zaistnienie powoduje skutki w systemie ale nie tak spektakularne jak ostatni event czyli Zakończono przejazdy. 

Dlaczego charakter lokalny?

Przypisanie numeru następuje w chwili potwierdzenia wprowadzenia opłaty przez zawodnik, czyli nie każdy zawodnik w jednej chwili wprowadza opłatę. Tak samo nie wszystkie dystanse są wystartowane w o tym samym czasie. Póki nie wystartował dany dystans można się na nie go zapisać. W chwili startu następuje zamknięcie pewnych funkcji ale otwarcie nowych.

Zdarzenie globalne

Zakończono przejazdy - te zdarzenie jest inne jego uruchomienie ma charakter globalny, obejmuje wszystkich zawodników, powoduje naraz zmianę kontekstu dla całego systemu. To zdarzenie zatrzymuje pomiar czasu, ten zawodnik który nie dał rady dojechać do mety albo punktu kontrolnego będzie nie klasyfikowany na swoim dystansie lub zdyskwalifikowany. To będzie określone w tym nowym kontekście poprzez reguły służące do klasyfikacji zawodników.

Podsumowanie

Zastanawiałem się nad istotą tego posta, w poprzednim już nie powstał kod, ale tu tym bardziej kod nie powstanie, bo może wyznaczenie granic i tych ważnych zdarzeń jest dużo bardziej ważne od samego kodzenia. W dzisiejszych czasach można go zlecić sztucznej inteligencji, pytanie czy te same AI może właściwie wyznaczyć granicę kontekstów, odnaleźć takie pivotal events, zrozumieć biznes i domenę?

Po co mi te zdarzenia? Dlaczego są one tak istotne dla procesu?
  • oznaczają granicę jednego kontekstu i wyznaczenie drugiego
  • zmieniają reguły i słownictwo, poprzez zmianę kontekstu
  • są drogowskazami, które informują o nieodwracalności
  • zabierają możliwości ale dają inne
Linki:

środa, 5 listopada 2025

Domain model cz3

 Tym razem inne podejście.

Wracamy do agregatu z poprzedniej części. Tyczy się on zdarzenia Zmiana Grupy Startowej. Mamy tam opisane takie reguły jak:

  • Grupy startowe muszą być wygenerowane,
  • Nie przekroczono terminu zmian grupy,
  • Grupa docelowa jest z tego samego dystansu,
  • Zawodnik nie zmieniał wcześniej grupy,
  • Grupa docelowa musi mieć miejsce.
Określiłem reguły typu "must be" (musi być spełniona) czyli:
  • grupy startowe muszą być wygenerowane,
  • grupa docelowa musi być z tego samego dystansu
Te dwie reguły są niezmiennikami, po analizie wyszło nam, że nie da się ich pominąć ani zmienić.
Pozostałe reguły nie są już tak "niezmienne", istnieją przypadki o których wspomniałem w poprzedniej części mówiące że można nagiąć te reguły.

Reguły możliwe do pominięcia 

Wracając do sedna, reguła
  • grupa docelowa musi mieć miejsce
Według logiki nie można zmienić grupy na taką która nie ma miejsca ale kontekst nagięcia tej reguły jest taki.
Mamy start zawodników mamy dwie grupy:
  1. grupa numer1 na 500 km 15 osób
  2. grupa numer2 na 500 km 1 osoba
Start grup odbywa się co 15 minut (co jakiś ustalony w regulaminie czas, dla przykładu 15 minut, nie pamiętam ile było w rzeczywistości), w moim przykładzie mam dwie grupy jedna 15 osób druga 1 osoba, pytanie czy ta jedna osoba ma czekać dodatkowe 15 minut? Ze względów organizacyjnych można taką osobę puścić w grupie poprzedniej. Grupy jako takie nie mają wpływu na sam przebieg maratonu, czyli są tylko zbiorami na zawodników, służącymi temu żeby zachować porządek i spełnić tę regułę. Grupie też przypisuje się start o tej samej godzinie.
Tak więc nic nie stoi na przeszkodzie żeby tego jednego zawodnika puścić razem z poprzednią grupą. Oficjalnie będzie występował w swojej grupie ale w chwili startu dwie grupy będą puszczone w jednej chwili.
Tak więc można dojść do wniosku, że jednak nie zostanie naruszona ta reguła. Mało tego ta reguła stanie się niezmiennikiem.

Analiza

W dojściu do tego wniosku pomógł mi Claude który stał się moim "sparing partnerem" i zamiast klepacza kodu stał się "gumową kaczką", która mówi.

Wcześniejsze podejście (poprzedni post) sugerował, że można dodać do kodu złamanie reguły, tudzież politykę, która umożliwiałaby taką sytuacje. Byłem świecie przekonany, że zatrudnię Clauda, któremu wrzucę problem i "wyklepie" mi rozwiązanie, ale podczas dodatkowej analizy, zdałem sobie sprawę, że to nie jest naginanie niezmiennika. To jest zupełnie coś innego co może śmiało być po za systemem, po za domeną.

Dlaczego muszę zawsze dążyć do tego żeby stworzyć kod? 
Może lepiej nie tworzyć kodu, a rozwiązanie. 

W tym przykładzie mam tego jednego zawodnika w grupie i drugą pełną grupę to mógłbym ja jako sędzia trasy w porozumieniu z organizatorami wypuścić takiego człowieka razem z grupą poprzednią. 
Czym jest grupa zawodników dla systemu? Jest zbiorem zawodników i spełnieniem reguły, że wystartować możemy maksymalnie grupę składającą się z 15 osób. Mamy na listach, czy w intrenecie wydrukowaną informacje, że start grupy jest o danej godzinie, dla systemu pomiaru czasu nie ma to żadnego znaczenia. Więc sędzia trasy, który wypuszcza zawodników wykorzysta interfejs aplikacji żeby wypuścić w tym samym momencie dwie grupy jedną 15 osobową i jedną 1 osobową.
Czyli pozostanie to w sferze rozwiązania poza systemowego, a to oznacza, że niezmiennik został obroniony. A samo rozwiązanie jest za pomocą "interfejsu białkowego".

Podsumowanie

 Nie wprowadziłem dodatkowego kodu i komplikacji do rozwiązania. Dzięki szerszemu spojrzeniu i zatrudnieniu Clauda w roli jakby drugiego analityka, który może spojrzeć z boku na problem, udało się nie pisać kodu. Wprowadzić rozwiązanie wykorzystując istniejące  możliwości.


środa, 8 października 2025

Domain Model cz.2

Dotarłem do miejsca w którym powstał kod. Oczywiście jak to jest w tych czasach korzystałem z pomocy Copilota. 

Do rzeczy, na warsztat został wzięty agregat Zmiana Grupy Startowej przez Zawodnika.

Widoczny pod tym adresem:

Generowanie grupy startowej

Wyszedł prawie idealnie, powstał prosty agregat jeden command jeden event, kilka reguł. Tu należy się uwaga, ten ES powstał jakieś cztery lata temu, razem z kolegami szkoliliśmy się w event stormingu padło na maraton, akurat ten temat był mi dobrze znany.

Kod jest polsko angielski, cóż tak uznałem, że będzie wygodniej na tę chwilę.

Opis:

Do zmiany grupy dochodzi na pewien czas przed startem maratonu, z reguły było to około miesiąca przed dniem startu. Następuje wtedy wygenerowanie grup startowych, które składają się docelowo z 15 zawodników. Istotnym faktem, który nie jest poruszany w tym kontekście ale należy go nadmienić jest to, że zawodnik który rejestruje się w danej edycji maratonu nie jest nazywany zawodnikiem a uczestnikiem.

Dlaczego to jest istotne? Uczestnik staje się zawodnikiem w momencie opłacenia startu, wtedy też uczestnik otrzymuje numer startowy, który uprawnia go do startu w zawodach oraz dzięki nie mu przynależy do jakieś grupy. Czyli jedną z reguł jest to, że do grupy startowej należą tylko zawodnicy.

Dlaczego to nie jest istotne dla tego agregatu? Dlatego że ten agregat umiejscowiony jest po fakcie wygenerowania grup. Dla niego fakt ten nie jest istotny, a są istotne inne reguły ale sama informacja jest ważna dla całego kontekstu zarządzania grupami startowymi i zrozumienia całego procesu.

Reguły

  1. grupy startowe muszą być wygenerowane - ta reguła w tym kontekście to typ "must be", bez wygenerowanych grup nie może odbyć się zmiana grupy, czyli brak spełnienia tej reguły eliminuje całość, nie da się jej nagiąc,
  2. nie przekroczono terminu zmian grupy - tu sprawa nie jest oczywista, termin jest płynny w zależności od edycji maratonu ale nie zmienia to faktu, że tylko w tym terminie można zmienić grupę *.
  3. Grupa docelowa jest z tego samego dystansu - to jest ważna reguła, której nie da się ominąć, czyli mamy kolejne "must be", dlaczego? grupy na danym dystansie startują o wyznaczonych porach, zapomniałem nadmienić że to maraton 24H, ta reguła została stworzona z przyczyn organizacyjnych, 
  4. Zawodnik nie zmieniał wcześniej grupy - reguła, ograniczenie ilości zmian, czy zawsze musi być spełniona? no to już zależy ale bardziej od reguł dotyczących danej edycji zawodów *.
  5. Grupa docelowa musi mieć miejsce - to dość logiczna reguła, która musi być spełniona, gdyż grupy mają narzuconą ilość zawodników, która ma tam być ale czy nie ma odstępstw? *.


Kod dostępny jest w tym miejscu


 public class ZmianaGrupyApplicationService
 {
     private readonly IGrupaRepository _grupaRepo;
     private readonly IZawodnikRepository _zawodnikRepo;
    
     public ZmianaGrupyApplicationService(
     IGrupaRepository grupaRepo,
     IZawodnikRepository zawodnikRepo)
     {
         _grupaRepo = grupaRepo;
         _zawodnikRepo = zawodnikRepo;
     }

     public void ZmienGrupe(ZmianaGrupyCommand command)
     {
         // 1. Pobierz dane
         var zawodnik = _zawodnikRepo.GetById(command.ZawodnikId);
         var grupaWyjsciowa = _grupaRepo.GetById(command.GrupaWyjsciowaId);
         var grupaDocelowa = _grupaRepo.GetById(command.GrupaDocelowaId);

         // 2. Utwórz agregat domenowy
         var zmiana = new ZmianaGrupyStartowej(
             command.TerminGrupy,
             zawodnik,
             grupaWyjsciowa,
             grupaDocelowa
         );
         // 3. Wykonaj operację domenową
         try
         {
             zmiana.ZmienGrupe(); 
         }
         catch (Exception ex)
         {
             //save to log
             throw;
         }

         // 4. Zapisz zmiany
         _zawodnikRepo.Save(zawodnik);
         _grupaRepo.Save(grupaWyjsciowa);
         _grupaRepo.Save(grupaDocelowa);
     }
 }

Nie będę ukrywał, copilot przyszedł mi z pomocą w tworzeniu tego serwisu, mój prototyp był nie co mniej "ładny". 

Agregat sprawdza reguły

 public void ZmienGrupe()
 {
     ValidateTermin();
     ValidateDystanse();
     ValidateZawodnik();
     ValidateIloscMiejsc();

     // Wykonaj zmianę na encjach
     _zawodnik.ZmienGrupe(_grupaDocelowa);
     _grupaWyjsciowa.UsunZawodnika(_zawodnik);
     _grupaDocelowa.DodajZawodnika(_zawodnik);

 }
Sprawdzenie reguł:
  • Nie przekroczono terminu zmian grupy - mogła zaistnieć sytuacja, pojedyncze przypadki, że tuż przed startem jakaś osoba mogła być przeniesiona z jednej grupy do drugiej, tak więc ta reguła może zostać podważona, ale to zawsze musiała być zgoda sędziego zawodów. Ciekawe jak to przedstawić w kodzie? (mamy rolę sędziego, to może być ciekawe)
  •  Zawodnik nie zmieniał wcześniej grupy - ta reguła mogła by być nagięta w sytuacji gdy zawodnik przed startem zgłasza że zmienia dystans, rezygnuje z jednego i pojedzie na innym dystansie,  dzieje się to po wygenerowaniu i zatwierdzeniu grup, sądzę, że ten proces jest inny i zasługuje na osobny agregat, reguły będą musiały być zbadane w szerszym zakresie, albo przypadek taki że kierownik zawodów ma jakąś znajomą osobę która jednak chciał by zmienić grupę, bo zapisała się już po zmianie grup i co odmówisz?
  • Grupa docelowa musi mieć miejsce - ta reguła może został "złamana" w przypadku gdy na danym dystansie mamy grupę, która osiągnęła limit zawodników i kolejną grupę która ma jednego zawodnika. Takie rzeczy się zdarzały i robiło się wtedy tak, że puszczało się jednego zawodnika z grupą wcześniejszą i czas startu jego grupy pokrywał się z czasem startu grupy poprzedniej,
Podsumowanie

Nagle nam się warunki zaczynają zmieniać, to chyba czas na zmianę agregatu i ponowne jego rozpisanie, a na razie cieszmy się tym co mamy.
Reszta kodu w repo.

Jak wspominałem event storming z maratonu ma już swoje lata, patrząc teraz na ten agregat widzę, że część reguł można nagiąć, a to sugeruje że, trzeba przejrzeć proces i sprawdzić ten kontekst.  Będzie szansa na to, bo jest opcja zrobienia warsztatów w tym temacie na żywo (zdalnie wyszły słabo). 

Na koniec powrót do przeszłości Historia wersji maratonu

sobota, 27 września 2025

Błędna decyzja

Jest błąd.

Czas żeby się z nim zmierzyć, wygląda na prosty

Błąd, posłużyłem się czatem gpt:

System.Data.SqlTypes.SqlTypeException:
  Wartość 0001-01-01 00:00:00 jest poza zakresem dla typu SqlDateTime.
  Zakres prawidłowy to od 1753-01-01 00:00:00 do 9999-12-31 23:59:59.

Pole w bazie, którego błąd dotyczy jest  typu DateTime i jest nullowalne, wykorzystywane przy okazji różnych funkcjonalności z danego kontekstu. Pole nazywa  się data ważności  dajmy na to  DateValid. Czyli mamy jakąś funkcjonalność, która ma określoną datę ważności, (do spożycia). 

To jest kontekst problemu który, powinien nam wystarczyć do jego zrozumienia.

Co robić?

Na pierwszy rzut oka problem jest prosty do rozwiązania, mamy nazwę pola możemy przeszukać w kodzie gdzie jest pole używane i zmienić wpisywaną wartość na tę z zakresu. Czyli z zakresu, który łyknie ms sql.

Opcja numer 2

Możemy też wejść do bazy i zmienić typ tej kolumny z DateTime na DateTime2, przy okazji do nie dawna nie wiedziałem, że jest taki typ jest, myślałem, że to błąd EJ-a, ten typ jest wprowadzony od wersji 2008. Jest to typ zgodny z .Net-otowym typem czyli przyjmuje .netowe wartości minimalne i jest bardziej dokładny. Tak właściwie powinno się go używać zamiast standardowego DateTime (żyłem w kłamstwie).

Wracając do tematu, mamy już dwie opcje w miarę proste do ogarnięcia, tylko czy właściwe?
Mamy pole DateValid jest nullowalne czyli może być puste, ktoś wpisuje tam minimalną wartość daty, te dwie informacje budzą we mnie pewne wątpliwości:
  • dlaczego wpisywana jest wartość minimalna skoro można wpisać null?
  • nazwa pola wskazuje że powinna być tam wartość, a ktoś tam wpisuje datę z poza zakresu data czyli minimalną można przyjąć,
  • być może któraś z funkcjonalności ma źle zwalidowane dane,
  • albo co gorsza jest proces, który dopuszcza wartość pustą w polu którego nazwa sugeruje, że taka nie powinna nastąpić, czyli błąd logiczny
Jak żyć?

Zacząłbym od przeszukania kodu, żeby ustalić która dokładnie funkcjonalność powoduje ten błąd, bo pole wiemy jak się nazywa, wiemy nawet co powoduje te wrzucenie daty minimalnej, powoduje błąd wrzucany do loga.

Dobra cze go nie wiem?

  • Jakie skutki ma ten błąd, proces jest przerwany, albo nieskończony,
  • Dlaczego wpisywana jest wartość minimalna daty, zamiast null-a,
  • Jaki jest sens tego żeby tam była wartość minimalna albo null bo to właściwe w tym przypadku to samo
Kolejny krok trzeba porozmawiać z kimś kto wie, czyli byznes.

No to błąd prosty do rozwiązania czy nie?


Jak widać nie koniecznie bo rozwiązanie proste typu zmiana typu w bazie lub poprawienie na minimalną datę zgodną z ms sql nie rozwiązuje problemu, wręcz przeciwnie tworzy nowe.

Tak więc pozostanie rozmowa z kimś, kto wie jak to ma działać, jak powinno działać. I naprawienie procesu. Czyli za małego błędu robi się góra lodowa, problem kryje się w głębi.
Dzięki temu, że przeprowadziliśmy analizę zagrożenia, a nie zaczęliśmy zmieniać kod może "uratowaliśmy świat".

Linki:


środa, 17 września 2025

A teraz z innej beczki

Ostatnio u nas w firmie pojawił się temat technicznego retro, zgłosiłem się na ochotnika, cóż w końcu trzeba się nauczyć występować.

Prezentowałem nietypowe podejście do pewnej funkcjonalności w naszym systemie. Nie będę tu zagłębiał się w szczegóły, ale w dużym skrócie, chodziło o relacje i podejście rekordowe w bazie danych. Było też trochę o normalizacji.

Pomyślałem, że to dobry pretekst do napisania jakiegoś krótkiego posta. Przez te kilka lat wyszedłem z wprawy. W między czasie szykuje się model z poprzedniego posta o DDD.

Do brzegu

Nie sposób przy tej okazji nie wspomnieć o tym, że podobny problem, opierający się na rozwiązaniu z wieloma kolumnami miałem w aplikacji do zarządzania maratonem rowerowym. Ten problem mogę tu przybliżyć, koncept jest podobny (zresztą na tym blogu kilka lat temu już o nim pisałem).

Problem

W aplikacji do maratonu rowerowego jedna z funkcjonalności, właściwie główna opierała się na pomiarze czasu, pomiarze czasu zawodników. Zawodnicy mieli karty RFID i odbijali się na poszczególnych checkpointach na 100 km-etrowych pętlach. 

Ilość checkpointów była zależna od dystansu na który zawodnik się zapisał, dystansów było kilka jak zaczynałem to były 100, 200, 300, 400, 500, 600 i 650 km, w kolejnych edycjach doszedł dystans 50 i 700. Zawodnicy startowali o różnych porach tak żeby zmieścić się w 24h, zapomniałem wspomnieć że to był maraton 24 godzinny.

Rozwiązanie - pierwsze

Jak to wtedy zamodelowałem?

Jako, że jestem fanem (mam nadzieje że już nie długo) techniki rozpoczęcia projektowania od stworzenia bazy, to zacząłem od zbudowania tabeli, która pozwoli mi na prosty i łatwy dostęp do czasu danego zawodnika. Oczywistym było zrobienie tabeli, która ma ilość kolumn równą ilości checkpointów, maksymalnej ilości checkpointów.

Problemy napotkałem w drugiej edycji, dotyczące tego rozwiązania, jeżeli ktoś się odbił dwa razy to drugi czas przechodził do kolejnego checkpointa - kolumny, jak ktoś zapomniał to znowu brakowało czasu. W tym miejscu trzeba nadmienić, że np dla jednego dystansu mieliśmy trzy checkpointy jak jednego brakło odbicia to mieliśmy przekłamanie.

Kolejny problem jest bardziej złożony, w którejś edycji dołożyliśmy checkpoint w takim modelu kolumnowym dołożenie kolejnych kolumn wiąże się ze zmianą modelu w backendzie i masą "logiku", szczególnie, że w pierwszych edycjach nie używałem entity frameworka a czystego ADO. Dodanie kolejnych wpisów musiało trafić w odpowiednią kolumnę, co też sprawiało problem.

Więcej problemów nie pamiętam.

Link do posta opisującego te rozwiązanie

Rozwiązanie - drugie, właściwe

W ostatniej edycji maratonu 2018/2019, maraton zaczynał się we wrześniu 2018 a konczył w czerwcu 2019, wpadłem na lepszy pomysł.

Tabela rekordowa zamiast kolumnowej, prosta zmiana dojście do tego zajeło mi 5 lat (pięć edycji).

Taka tabela jest znacznie prostrza, nie ma tyle kolumn, a cała logika zapisu polega na wierszach w takiej formie nie potrzebujemy kolumn na kolejne checkpointy, bo wystarczy nam jedna kolumna prezentująca czas.

Rozwiązanie jest zaprezentowane w w tym poście. Jakie są jeszcze zalety?

Przy takiej formie tabeli, nie muszę dbać o czasy pośrednie, bo tak naprawdę liczy się dla mnie tylko czas pierwszy i ostatni ich różnica da mi czas który jest tak naprawdę istotny czyli czas przejazdu całego dystansu. Owszem mogę liczyć checkpointy i odbicia ale czy ich będzie 1 czy 10 nie ma to różnicy w tym podejściu. Samo wyliczanie przejechanych checkpointów odbywa się po stronie aplikacji. Ilość checkpointów nie gra roli.

Wnioski

Warto czasem rozważyć, czy tabela kolumnowa w dłuższej perspektywie czasu będzie dobrym rozwiązaniem? Czy przyniesie więcej korzyści przekształcenie jej w tabele wierszową?

To też w dużej mierze zależy od problemu, w aplikacji z maratonu rowerowego, pomiar czasu i związane z tym problemy przy rozwiązaniu kolumnowym otworzyły mnie na głębsze zastanawianie się nad zastosowaniem właśnie podejścia rekordowego, co też teraz często stosuje.

Ale rozwiązanie wierszowe jest trudniejsze na start, opiera się też między innymi o relacje.

Przykład inny, spotykam się często zrobieniem list danych, ograniczonych zbiorów na kolumnach tabeli co jest dla mnie sygnałem że takie rozwiązanie może się szybko zemścić, gdy nagle się okaże że listę danych trzeba rozszerzyć.

A co do wpisywania list do tabel rekordowych? Dla mnie jest to oczywiste ale wiąże się z tym ze daną tabele z taką listą trzeba wpiąć przez relacje do głównej tabeli która tę listę zawiera, potrzebuje.

Jeżeli mamy relacje to przy zapytaniu potrzebujemy joina, co wpływa na wydajność, rozwiązanie kolumnowe jest szybsze, nie ma joina (chyba że są setki kolumn).

Kolejna "słabość", to transakcje. Bardzo możliwe że będziemy potrzebowali użycia transakcji do obsłużenia wielu tabel wchodzących w skład tabeli głównej.

Czy to jest zawsze opłacalne? To pytanie jest otwarte, warto sobie je zadać przy projektowaniu i podejmowaniu decyzji, bo za niewłaściwe zapłata przychodzi często z opóźnieniem.


Nowe otwarcie - powrót po latach

 Nowy początek - przemyślenia


Ponad cztery lata po stworzeniu event stormingu na temat aplikacji "Maraton Rowerowy" (kawałek event stormingu plus kod) i sześć lat po zakończeniu ostatniej edycji (kod do ostatniej edycji), postanowiłem wrócić do tego tematu.

Zachęciła mnie do tego chęć przybliżenia sobie wzorca Domain Model. Być może powinienem go znać, gdyż ES z maratonu powstał na bazie szkolenia DNA gdzie wałkowane było DDD, ale wtedy nie zrozumiałem dobrze tego wzorca. Kod napisany po poprzednich częściach DNA był no cóż kiepski.

Po pięciu latach zrozumiałem że sercem DDD jest model i stąd ten pomysł żeby w końcu spróbować za modelować, porządnie, jakiś kontekst z maratonu. 

Każda firma w której pracowałem, miało jakieś legacy, projekty, które uczą jak nie programować, do których nie wchodzi się z uśmiechem na twarzy, a bardziej z trwogą. Czasami sam robiłem takie legacy i zostawiałem w odmętach danej firmy, tak było jak pracowałem na kopalni. 

Jeżeli chce innego wyniku to nie mogę nonstop rozwiązywać problemu w taki sam sposób. 

W obecnej firmie,  też mamy legacy system, ale zmieniło się coś we mnie zacząłem dostrzegać w nim potencjał, ma on swoje ciemne strony, ale ma też spore pole do refaktoringu, zrozumienia naszego biznesu. Legacy to nie tylko problem ale to część czyjeś historii, zestaw decyzji, które zaważyły na aktualnym stanie. Czas pokazuje, że nie wszystkie były trafne ale czy to powód do krytyki? nie sądzę.

Wróciłem do książki  Praca z zastanym kodem, sięgnąłem również po książkę Martina Fowlera, dotyczącą refaktozyzacji, mam też szkolenie Legacy Fighter i w tym ostatnim droga prowadzi z legacy do DDD.

Nie mogłem też w moim zestawieniu pominąć Andrzeja Krzywdy występującego w podcaście Better Software Desing mówiącym o refaktoryzacji. Te lektury, sprawiły że legacy zacząłem widzieć w innym świetle, legacy stało się wyzwaniem nie tylko problemem.

Świat związany z programowaniem zmienia się tak szybko, nie nadążam już, jestem człowiekiem po czterdziestce, czasami potrzebuje czegoś stabilnego, czegoś czemu mogę zaufać, czegoś niezmiennego.

Są takie rzeczy od lat stałe, choćby normalizacja baz danych, koncept z przed półwieku, taki stary to każdy go pewno zna i rozumie. Nic bardziej mylnego. DDD, przecież to ma z 20 lat, kogo się nie spytasz to zna DDD i jeszcze w tym pracuje, a jak zgłębisz temat to jest tak samo jak z normalizacją.

Może nie trzeba szukać nowych frameworków, może wystarczy zrozumieć te stare, wciąż aktualne koncepty? zrozumieć i używać, ale używać tam gdzie to jest potrzebne.

Ostatnia rzecz, która wydaje się niezmienna od lat, to potrzeby klienta, użytkownika, biznesu i zrozumienie tego, zrozumienie ich.

Krótka historia.

Pisałem aplikacje do obsługi maratonu rowerowego w latach 2013 - 2019. Co roku tworzyłem na nowo, uczyłem się na tym, nie było jeszcze AI, który by mi wygenerował kod, może coś by wymyślił, podpowiedział, poprawił. Jak kończyłem ostatnią edycje pojawił się temat event stormingu, zachwyciło mnie te podejście, to była końcówka roku 2018, jeździłem wtedy jeszcze na meetup-y, zobaczyłem ES i spodobało mi się. Odkryłem narzędzie którego mi brakowało, sposób na zrozumienie, dogadanie się z drugą stroną.

Dlatego poszedłem na DNA?, bo był event storming, reszta mnie nie interesowała, w między czasie przeczytałem DDD Erica Evansa (strasznie nudna książka). 

Rok temu zacząłem, na nowo odkrywać DDD, wracać do ES, wydaje mi się że musiało minąć 5 lat żeby coś zrozumieć, coś wykiełkowało, doszło do mnie. Do książki Erica Evansa też wróciłem, już nie jest nudna.

Ale ta droga nie była taka jak sobie ją na początku wymarzyłem, po kilku próbach z kolegami, po których pozostały :

Przyszedł czas na test w boju, dostałem zadanie napisania aplikacji do zarządzania przedsiębiorstwem produkującym, wynajmującym i sprzedającym automaty do gier. Żeby zrozumieć proces i kontekst, zaproponowałem event storming. Spotykaliśmy się przez dwa miesiące, bo to był projekt który robiłem po pracy, zaczęło się i skończyło na ES-ie. Po analizie trzeba było zrobić wycenę, poszło o kasę, ale robiąc event storming już wiedziałem że nie dam rady tego "wyklepać", miałem za małe doświadczenie, umiejętności.
Teraz jak bym dostał takie zadanie, nie próbował bym go zrobić, raczej spróbował bym co się da oddelegować na gotowe narzędzia i zaproponować integracje, a kodzić jak najmniej. Mądry ten kto napisał że "najlepszy kod to taki który nie istnieje".

Te doświadczenia spowodowały  u mnie odejście od event stormingu, skupienie się nad poprawą warsztatu. Nie da się jednak uciec od problemów, od legacy. Można narzekać na taki kod ale to on często przynosi największą wartość firmie. To on ma największy potencjał do zmiany, niełatwej ale możliwej, to tu można się najwięcej nauczyć.

Dlaczego powrót do Event Stormingu? Dzięki nie mu mogę lepiej zrozumieć proces, odkryć reguły, znaleźć granicę, ale jest coś ważniejszego. Ta druga strona, biznes, może współtworzyć  to, może być częścią tego procesu wyklejania karteczek i może go rozumieć. Tekst wszystkiego nie ogarnie, nie w taki sposób jak tablica na Miro, gdzie będę mógł widzieć cały proces i powiększyć sobie kawałek z niego, przejść przez "Happy patha" ale też wyznaczyć ścieżki mniej oczywiste. Nie będzie potopu słów którego nie jestem w stanie szybko ogarnąć bez robienia sobie dodatkowych notatek.

Zdobywanie wiedzy od biznesu i tak polega na rozmowie tylko zamiast "excela", dostaje borda z karteczkami.

Plan

Post ten powstał żeby na nowo zrozumieć DDD, wzorzec domain model i sam event storming ale w tej kolejności. Głównym zadaniem będzie nauczyć się modelować i w tym pomocny będzie temat "Maratonu rowerowego", oraz event stormingu który został dla nie go stworzony. To ułatwi mi następujące etapy:

  • rozpisać jeden lub dwa konteksty odkryte na event stormingu z maratonu rowerowego,
  • rozpisać scenariusze w obrębie wybranego kontekstu, coś takiego jest w rozdziale o budowaniu agregatów w książce Erica Evansa o DDD,
  • na koniec chce spróbować to zamodelować w kodzie 
To co robiłem do tej pory opierało się o modele anemiczne nawet jak logika byłą większa, potrzebowałem kolejnego roku żeby to zrozumieć, żeby nie bać się modelu, to tam ma być logika nie gdzie indziej.

Pozostała też kwestia, że w mojej pracy starałem się promować model anemiczny, co było błędem z mojej strony, ktoś by mógł zadać pytanie przecież zrobiłeś kurs DNA, czytałeś Evansa, słuchałeś podcastów, czytałeś artykuły, to gdzie to wszystko się podziało?
Dobre pytanie, może tak naprawę nigdy nie rozumiałem? Może potrzebowałem czasu żeby dostrzec te rozwiązaniem bo nie zawsze DDD jest słuszne czasami model anemiczny pasuje, relacje w bazie są właściwe a czasami, potrzebuje tabel bez relacji albo bazy nierelacyjnej.
Wydaje mi się że w moim przypadku to czas, problemy, kod, coraz częstsze pytania i wątpliwości z powrotem doprowadziły mnie do tego miejsca.




niedziela, 1 grudnia 2024

 Plan jest taki

Postanowiłem stworzyć projekt związany z webapi, na początek będą się tu znajdować luźne przemyślenia, na temat tego zdania. Ogólny zarys projektu i możliwe rozwiązania jakie zastosuje. Projekt ten będzie czystko szkoleniowy, chciałem spróbować nowego podejścia do tworzenia aplikacji, postanowiłem, że będę to dokumentował tutaj.

Dawno nie robiłem projektu prywatnego, dawno nie pisałem nic na blogu, tym czy poprzednim, miałem sporą przerwę. Teraz pojawiła się chęć odświeżenia dokumentowania pracy na blogu.

Projekt ma roboczą nazwę VehicleApi.

Aktualnie w pracy zajmujemy się aplikacją do obsługi wózków widłowych. Temat ten jest już mi dobrze znany, bo mija rok od momentu w którym dołączyłem do tego projektu. Został on przeze mnie mocno zrefaktoryzowany (zmieniony), zadecydowało o tym błędne zrozumienie domeny problemu.

Aplikacja, aktualnie działa na produkcji od kilku miesięcy, ale nie ma jeszcze pełnej funkcjonalności. Jest ciągle rozwijana. W tym projekcie pokusiłem się o zastosowanie architektury warstwowej idącej w kierunku DDD, z podziałem na warstwę

  •  aplikacji
  • domeny
  • infrastruktury
Temat wózków okazał się bardzie złożony niż na początku się wydawało. A same podejście do architektury nie weszło od razu w powyższy podział. Jak wspomniałem ten projekt powstał dużo wcześniej, a to co ja zaproponowałem to była głęboka zmiana istniejącego rozwiązania.
Tak więc w miarę przyrostu wiedzy, serwisy które oznaczały dany problem rozwarstwiały się tworząc architekturę trójwarstwową.

Dlaczego wspominam o tym projekcie? Gdyż będzie on moim punktem wyjścia, dla rozważań na temat architektury, jest to projekt, który działa i rozwiązuje rzeczywisty problem. VehicleApi będzie projektem który będzie nawiązywał do tamtego ale skupiał się nad przetestowaniem innych podejść, do problemów.

Opis projektu

Po tym przydługim wstępie, skupię się na głównym temacie tego posta.


Pierwszy krok za mną, projekt wrzucony na githuba ;-)

Założenia:
  • frontem będzie webapi,
  • każda warstwa będzie osobnym projektem,
  • podstawą wyjścia będzie warstwa aplikacji,
  • będzie warstwa domenowa z bardziej złożoną logiką,
  • warstwa infrastruktury dostępu do danych,
Z zastosowanych technologii:
  • .net 6,
  • MediatR,
  • FluentValidation,
  • EF core code first
Z czasem listy będą się powiększać, ale na początek to wystarczy. Projekt ma też za zadanie przećwiczenie technologii i przetestowanie innego podejścia.

Co do samej domeny, czyli jakieś głębszej logiki pojawi się ale później jak się uporam z postawieniem tego projektu. 
Co do testów też się pojawią będzie to Nunit i Xunit.

Do dzieła..... 

Domain Model cz4

 Kolejny raz bez kodu, kolejny raz z Cluade-m w roli analityka. Głównym tematem dziś będą Pivotal Events i bounded contexty. Na sesji Event ...