Non-anemic entities

0
Bambo napisał(a):

@jarekr000000:

Ja to ogólnie chciałbym trafić do apki w Javie/Kotlinie, która nie jest CRUDem. No chyba, że ten CRUD to buzzword. No bo co .. czy jeśli wystawiam resta, ale tak naprawdę między wyciągnięciem z DB, a wypluciem na front mam jakieś jeszcze pobieranie danych z innych mikroserwisów, liczenie czegoś, transformowanie tych danych to nadal to jest CRUD ? Wiadomo, że ciekawsze jest np. pisanie sterowania robotem w C na mikrokontrolery, ale ciężko mi sobie wyobrazić w Javowym świecie coś innego niż REST :(

@Bambo Też bym nie pogardził. Pojeździ człowiek na konferencji, posłucha mądrzejszych od siebie i potem musi wracać do tych smutnych projektów które w dużej mierze w PL się developuje. Można mówić że wystarczy zmienić firmę ale naprawdę przerabiałem to już kilka razy i z droświadczenia wiem że większość projektów to lipa :(

Btw @jarekr000000 jeszcze jedno pytanie do mojego przypadku opisanego poprzednio. Jeśli na froncie robię jakąś wstępną walidację danych w JS a potem mam tam jeszcze spring MVC i walidację w kontrolerze (np. jsr-303) to kiedy wysyłam i odbieram te dane na serwisie backendowym to jest sens ponownie to sprawdzać? Np. Rejestracja usera. Na froncie 2 razy trzeba podać hasło, do kontrolera mvc trafia także hasło i powtórzone hasło żeby zwalidować dane po stronie serwera ale potem jak chcę to wysłać do serwisu obsługującego userów to jest sens wysyłać to jeszcze z obydwoma hasłami? Wydaje mi się że nie ale z drugiej strony serwis dostarczający jakieś tam funkcjonalności użytkownika nie ma pewności że dane które dostaje są zawsze zwalidowane bo jakiś dev może wysłać mu coś niedokońca poprawnego. Z drugiej strony czy to już nie przesada żeby tyle razy sprawdzać dane?

0

Z reguły ten backendowy serwis będzie kompletnie niedostępny dla świata zewnętrznego, więc może "ufać" klientowi, którym będzie adapter, który wykonuje już wszystkie walidacje. Mieszanie logiki front-endowej do tego backendowego serwisu (walidacje identyczności haseł, regexpa emaila itp.) nie mają sensu, a nawet powiedziałbym że specyficznie przypadek walidacji haseł to byłaby tragedia (sprawdzania regexów jeszcze OK ale raczej tylko gdy nie ufasz, że klient to zrobił - w przeciwnym wypadku nie ma sensu marnować czasu i zasobów).

1

@eL Co do walidacji i gdzie to zależy.
Wiadomo, że na wejsciu aplikacji (serwis http) walidujemy ostro i dajemy sensowne komunikaty błędów.
Głębiej: to zależy.
Jak duży zespół, jak stary projekt itd.
Asercje (precondition itp). kosztują mało, a chodzi tylko o to żeby szybko się wywalić zanim szkody będą duże. I precodnidions z guavy lub javowy assert i Object.requireNonNull większość takich przypadków mi załatwiają.

Nie przesadzam. Stosuję tu też zasadę pokojową. Czyli jeśli z modułu, api, klasu bedą korzystać tylko ludzie z mojego pokoju/zespołu to raczej odpuszczam. Bo w razie problemu ludzie szybko sobie zajrzą w kod.
Ale sąsienich pokojów / zespołów do tego nie zmuszam. Oni muszą mieć szybko i jasno podane co jest zmlaszczone.

2

Dorzucę swój ulamek grosza: co do przykładu eL to bym wywalił walidację z kontrolera i walidował wszystko na backendzie (w module, 'corze'), z doświadczenia to spotkałem się w większości z sytuacjami gdzie front to jest delikatna walidacja, a backend bardziej konkretna + ta delikatna, czyli np. regexp na mailu, ale też sprawdzenie unikalności. Robiąc walidacje na kontrolerze po adnotacjach IMHO nie oszczędza się wielkich ilości czasu względem napisania tego 'normalnie', a jeżeli jest walidacja w backendzie to i łatwo to unitowo przetestować i wzrasta 'przenoszalnośc' kodu, bo wszystko jest w jednym miejscu i nie wycieka na kontrolery :)

0

@hcubyc: @eL @jarekr000000

Pytanie odnośnie zasady zapisywania jednego agregatu w jednej transakcji. Wiem, że zasadę można łamać jak każdą, ale ja lubię to zgłębiać. Tak sobie myślę, że to jest mega ciężkie to zrealizowania w pewnych przypadkach. Jak to jest np stosowane w książkowych przypadkach przelewów ? Tu zawsze są updatowane dwa agregaty przecie - od jednego odejmowane pieniądze, do drugiego dodawane.

Ja również w swoim przypadku mam taką operację, która jednemu użytkownikowi odejmuję punkty, drugiemu dodaje, a temu co wywował operację robi jeszcze coś innego - modyfikuję tutaj 3 agregaty.

Wychodzi na to, że jednak User jest encją i mam ją opakować jakimś agregatem innym ? Robi się hardkorowo.

Trochę wtf...

1

@Bambo: i dlatego właśnie przelewy bankowe nie działają tak, jak opisane jest w książkach. W takich przypadkach masz jeden agregat, który reprezentuje przelew. Stan kont jest wyznaczany na podstawie historii operacji i po procedurze rekoncyliacji.

W twoim przypadku masz nadal jeden agregat, ale korzysta on z informacji zebranych z kilku encji.

0

@Koziołek:

czyli to działa tak, że jest agregat przelew, który zawiera info o kwocie oraz accountFrom i accountTo, a jak się wylicza stan konta usera to po prostu się jakoś sumuje po wszystkich krotkach zawierająca nr konta tego usera ?

No właśnie nie za bardzo wiem jaki agregat powinienem mieć u siebie. Na razie mam usera z takimi polami jak id, mail, name, points i rank. Czuję, że to za dużo i zastanawiam się jak to zamodelować, żeby modyfikacja tych 3 userów była w 1 agregacie. Chyba, że analogicznie mam zrobić jakiś Agregat, który będzie reprezentował taką pojedyńczą akcję, czyli miał w sobie idUsera, dla którego dodaje punkty, idUsera dla którego odejmuję punkty, liczbę tych punktów i coś tam jeszcze .. no sam nie wiem.

0

Ok rozkminiałem to wczoraj i faktycznie chyba zakumałem to, ale wolę się upewnić.

Jeśli mam przypadek, gdzie mam pewną operację, załóżmy jakąś walkę między 2 userami to zamiast w agregacji User dawać pola jak ranking i updatować je podczas każdej takiej walki powinienem zrobić raczej agregat walka i przetrzymywać tam id wygranego i przegranego tak ? I rozumiem, że wtedy mam zrobić sobie jakiś widok w bazie, który będzie podliczał ich rankingi przez jakieś funkcje grupującego z SQL ? Czy są jakieś lepsze sposoby na podliczanie tego ? Tworzenie snahpshotów co jakiś czas ?

0

@hcubyc: @Koziołek @jarekr000000

Stwierdziłem, że przeprojektuję całą domenę w tej gierce przeglądarkowej. Robienie agregatu, który zawiera surowce, budynki i flotę to był zły pomysł, bo łamanie SRP jest potężne, dzieje się w tym agregacie masakrycznie dużo i jak chciałbym poszerzyć jakąś funkcjonalność to już w ogóle masakra. Fakt, że zapisuję 1 agregat w transakcji, ale no ..

Wpadłem na inny pomysł ! Funkcjonalność upgradowania budynków będzie jako osobny bounded context. Mamy wtedy agregat Operation, który ma takie atrybuty jak id bazy, czas startu oraz typ upgradowanego budynku, czyli wszystkie informacje, które potrzebujemy, aby zapisać fakt zaczęcia operacji upgradowania. Tylko teraz tak ... nie wiem w jaki sposób przechowywać informacje o surowcach:

  1. Mógłbym w agregacie bazy militarnej trzymać info o surowcach, ale wtedy przy zapisywaniu nowej Operacji muszę w 1 transakcji zapisać też bazę, więc łamię zasadę 1 agregat per transakcja
  2. Nie muszę zapisywać stanu aktualnych surowców tylko za każdym razem kiedy je potrzebuję liczyć je na podstawie operacji z przeszłości dla danej bazy. No właśnie .. tylko, że nie same operacje wpływają na surowce :). Również jakieś kopalnie wydobywają liczbę surowców / sekundę. Mogę zrobić agregat Refreshing, który zawiera info o czasie odświeżenia i liczbie surowców, które przybyły i wtedy licząc Refreshingi i Operations mam info o liczbie surowców. Z drugiej strony bardzo często muszę znać aktualny stan surowców, więc liczenie tego chyba nie jest super wydajne, więc chyba jednak fajnie znać ich obliczony do tej pory stan ...

Jak to ugryźć ?

0

łamię zasadę 1 agregat per transakcja

Tak wspominałem o tym wcześniej, ale to tylko zasada, którą świadomie można złamać

Z drugiej strony bardzo często muszę znać aktualny stan surowców, więc liczenie tego chyba nie jest super wydajne, więc chyba jednak fajnie znać ich obliczony do tej pory stan

Normalnie można mieć osobno historię zdarzeń i osobno pogrupowane te zdarzenia, tak że np. nie musisz pobierać historii surowców od początku istnienia bazy tylko pobierasz grupę/ostatnio obliczony stan. IMHO to jednak byłoby przekombinowanie w tym przypadku
Poza tym co ci da wyniesienie operacji z bazy? Zakładam, że operacja to nie jest jakiś mega generyczny mechanizm tylko zależny od BC bazy. Więc do osobnego bounded contextu (operation) chcesz wynieść 'akcje' na jeszcze innym bounded contexcie (baza)? BTW byłoby łatwiej gdybyś wrzucił jakiś prosty schemat graficzny co jest czym i co robi

0

Na szybko jakie mam operacje:

  • upgradowanie budynku (sprawdzenie czy dany budynek się już nie buduje, sprawdzenie czy mam surowce, następnie zapisanie stanu, że dany budynek zaczął się właśnie upgradować, odjęcie surowców i zmniejszenie dostępnych zasobów energetycznych)

  • odświeżenie stanu bazy militarnej (zwiększenie liczby surowców na podstawie poziomu kopalni i czasu ostatniego odświeżenia, sprawdzenie czy jakaś operacja upgradowania się już zakończyła - jeśli tak to zwiększenie dostępnej energii no i też jakoś trzeba będzie wyliczył przyrost surowców odpowiednio, bo jeśli załóżmy ostatnie odświeżenie surowców miałem 2h temu, a kopalnia skończyła upgrade 1h temu to muszę dodać 1h * stary przelicznik + 1h * już ten większy)

Oczywiście dochodzi jeszcze budowanie floty, wysyłanie floty, bdanie technologii, ale to powiedzmy, że później. Na razie te 2 główne funkcjonalności.

0

Wg mnie to funkcjonalności o których piszesz w pierwszym podpunkcie powinny być w odpowiedzialności budynku - ciężko znaleźć sensowne porównanie, ale to tak jakby pytać kolegi czy masz w portfelu odpowiednią ilość pieniędzy na wyjście na miasto - ciebie powinno się pytać. Niestety jest problem z takimi pytaniami/odpowiedziami, bo jedna i ta sama domena może być zamodelowana na kilka sposobów, co zależy głównie od tego jakie funkcje dany moduł ma realizować i w jakim kontekście. Dlatego pytałem o szerszy schemat graficzny co jest czym i z czym będzie gadać, bo wtedy będzie już jakis szerszy obraz. To co mi się wydaje, że powinno byc realizowane przez budynek może sie w gruncie rzeczy nie sprawdzić w twoim przypadku, poprawne zamodelowanie domeny to dość nietrywialne zadanie i w gruncie rzeczy cięzko to zrobić raz a dobrze, zazwyczaj sie ostro refaktoruje w trakcie, także to nic złego ;)

0

Polecam super hacka. Wbrew pozorom jest zupełnie dobry:

  • cały system to jeden agregat :-),
  • ewentualnie: wszystko danego gracza to jeden agregat, ( i tu znowu się pojawia problem jak zamodelować interakcje między graczami, ale można zrobić osobny agregat (na wszystkie), albo wiele osobnych per para graczy),
0

@jarekr000000:

A Ty jak odnosisz się do zasady zapisywania więcej niż 1 agregatu w transakcji ? Uważasz, że jeśli tak trzeba zrobić to już coś zaczyna śmierdzieć z domeną i trzeba przeprojektować czy olewasz ?

Mam taki własnie case z zamodelowaniem rywalizacji między graczami, załóżmy jak w szacach - rankingu elo. Czyli jest mecz między userami, jeden z rankigiem 2200 wygrywa, drugi z rankingie 2100 przegrywa, jest liczony nowy ranking i są zapisywani. No i tutaj mamy zapisywanie 2 agregatów - 2 userów. Myślałem, żeby np. zrobić agregat Match, który zawiera info o id obu graczy, ale w ten sposób nie znam ich rankingów i nie mam jak policzyć nowych

0

Dla mnie tam jest jeden agregat - ranking.
I btw. jeśli robisz system na bazie danych SQL to w praktycznie zawsze masz tylko jeden jedyny agregat: twoja baza danych (jej stan).

1

No i co, Ranking zawiera id oraz wartosc rankingu jednego i drugiega usera ?

Nie bardzo rozumiem jak chcesz odczytać informację o dotychczasowym rankingu graczy i potem w jakiej formie zapisać nowe.

Edit:

Myślałem, że db nie powinno wpływać na samą aplikację. Chyba, to że mam SQLa nie powinno determinować architektury ?

Ale osobiście starasz się przestrzegać tego zapisu 1 agregatu na transakcję ?

1 użytkowników online, w tym zalogowanych: 0, gości: 1