Non-anemic entities

1

@student pro zupełnie nie rozumiem o co pytasz. Co ma piernik do wiatraka? Zapisujesz sobie w jakimś persistent store to co potrzebujesz zapisać. Jeśli potrzebujesz zapisać tylko jakąś konkretną wartość, to nie ma sensu persystować wielkiego grafu obiektów domenowych. Poza tym od lat promowane są rozwiązania poloyglot persistence, gdzie masz wiecej niż jeden persistent store, żeby zapisywać dane w logicznych dla nich sposób. Część danych zapiszesz do jakiegoś key-value store, inne do bazy dokumentowej, inne do relacyjnej a jeszcze inne do czegoś innego. Tego nie da się zrobić jakimś ORMem ;]

Relacyjne bazy danych to też nieporozumienie z funkcjonalnego punktu widzenia

o_O Relacyjne bazy doskonale sprawdzają się w wielu zastosowaniach. Nieporozumieniem jest wpychanie do nich na siłę danych które zwyczajnie tam nie pasują. Ale to jest problem z każdą technologią używaną źle. Klasyczny przykład sytuacji gdzie jeśli masz młotek to wszystko wygląda jak gwoździe. Niestety ludzie tak robią. Mamy w projekcie bazę SQL? No to ładujemy tam wszystko jak leci, zamiast dołożyć po prostu obok jakiegoś redisa czy elasticsearcha.

0

Poza tym od lat promowane są rozwiązania poloyglot persistence, gdzie masz wiecej niż jeden persistent store, żeby zapisywać dane w logicznych dla nich sposób. Część danych zapiszesz do jakiegoś key-value store, inne do bazy dokumentowej, inne do relacyjnej a jeszcze inne do czegoś innego. Tego nie da się zrobić jakimś ORMem ;]

Możesz podać jakieś przykłady.?

Albo artykuły, książki, które pomogą zrozumieć to co do czego i kiedy?

0

Tak w przybliżeniu. Jakie dane do jakiej bazy?

0
Shalom napisał(a):

@student pro zupełnie nie rozumiem o co pytasz. Co ma piernik do wiatraka? Zapisujesz sobie w jakimś persistent store to co potrzebujesz zapisać. Jeśli potrzebujesz zapisać tylko jakąś konkretną wartość, to nie ma sensu persystować wielkiego grafu obiektów domenowych. Poza tym od lat promowane są rozwiązania poloyglot persistence, gdzie masz wiecej niż jeden persistent store, żeby zapisywać dane w logicznych dla nich sposób. Część danych zapiszesz do jakiegoś key-value store, inne do bazy dokumentowej, inne do relacyjnej a jeszcze inne do czegoś innego. Tego nie da się zrobić jakimś ORMem ;]

Relacyjne bazy danych to też nieporozumienie z funkcjonalnego punktu widzenia

o_O Relacyjne bazy doskonale sprawdzają się w wielu zastosowaniach. Nieporozumieniem jest wpychanie do nich na siłę danych które zwyczajnie tam nie pasują. Ale to jest problem z każdą technologią używaną źle. Klasyczny przykład sytuacji gdzie jeśli masz młotek to wszystko wygląda jak gwoździe. Niestety ludzie tak robią. Mamy w projekcie bazę SQL? No to ładujemy tam wszystko jak leci, zamiast dołożyć po prostu obok jakiegoś redisa czy elasticsearcha.

Odnośnie tego że nie rozumiesz o co pytam- po pierwsze te obiekty biznesowe i tak muszą być załadowane w całości (w całości w sensie early/lazy loading) specyficznie dla danego przypadku użycia. W jednym przypadku chcemy część atrybutów załadować wcześnie, czasem późno, czasem chcemy jedne obiekty zablokować, czasem nie. Fakt- rzadko w praktyce takie sytuacje się zdarzają, ale jednak. No ale powiedzmy, że wszystko jest załadowane wcześnie. Obiekty ze sobą rozmawiają, wywołują swoje metody, ich stan się zmienia. Jak magicznie logika biznesowa bez wyraźnej zależności do systemu utrwalania jest w stanie powiedzieć co tak naprawdę chce utrwalić? Właśnie to jest jedna z najważniejszych zalet Hibernata - automatyczna detekcja zmian (dirty checking). Jeśli sam miałbym wszystkiego pilnować to używanie Hibernate'a jest coraz bardziej dyskusyjne.
Odnośnie relacyjnych baz - ja jestem przyzwyczajony tylko do nich, wyraźnego schematu, dla mnie dokumentowe bazy są podobne do JavaScriptu- nic się qpy nie trzyma. Wydaje mi się że znam je dość dobrze, to jak naprawdę działają mechanizmy izolacji, manualne zamki, mvcc, indeksy (bo te tematy mnie interesowały). Ale nie możesz podważyć tego że jak klient chce zobaczyć dane w relacjach (w hierarchicznej strukturze) to trzeba to zrobić ręcznie albo za pomocą ORMa. Robię JOINa, łączę samochody z ich właścicielami, klient chce to zobaczyć w hierarchicznej strukturze, dostaję płaską tabelę. No coś trzeba z tym zrobić. Wystarczyłoby żeby RDBMsy zostawiały fazę "renderowania" wyniku do ostatniego momentu, trzymając relacje w pamięci (relacje w sensie co było z czym zjoinowane) i umożliwiłoby to przedstawienie wyniku w jsonie, xmlu czy czymkolwiek.
edit aha no i traktuję to jako rozmowę a nie fanatyczne przekonywanie kogoś do swoich pomysłów, narzędzia cały czas się zmieniają

0

Nie jestem pewien ale czy event sourcing i cqrs nie jest własnie trochę innym podejsciem niż standardowy hibernate z dirty checking?

0

@Pijany Rycerz google: polyglot persistence
@student pro nie wiem, może pracujesz przy innych systemach niż ja, ale nie widziałem jeszcze sytuacji gdzie musiałbym persystować cały graf obiektów aplikacji. Zwykle persystuje się pewne konkretne dane i tyle. I to jest też część logiki biznesowej -> decydować o tym co zapisujemy. Przecież w aplikacji możemy mieć dane zebrane z wielu miejsc! Po co je duplikować bez sensu? Zwykle zapisujesz tylko to co ma być utrwalone, a stan obiektów domenowych generujesz w locie na tej podstawie. Takim ekstremalnym przykładem jest event sourcing ;]

Ale nie możesz podważyć tego że jak klient chce zobaczyć

Dopasowywanie mechanizmu persystencji pod widok to jakiś idiotyczny pomysł. Dane składuje się w logiczny dla nich sposób i tyle, niezależnie od tego czy ktoś potem chce tabelkę czy drzewko. Bo jak klient zażyczy sobie zeby mu pokazywać i drzewko ale też i tabelkę (do chce do excela skopiować) to co wtedy? :D

Wystarczyłoby żeby RDBMsy zostawiały fazę "renderowania" wyniku do ostatniego momentu, trzymając relacje w pamięci

Obawiam się ze nie masz pojęcia jak działają algorytmy joinowania wyników ;)

0
danek napisał(a):

Nie jestem pewien ale czy event sourcing i cqrs nie jest własnie trochę innym podejsciem niż standardowy hibernate z dirty checking?

Szczerze to raczej myślałem o czymś na niższym poziomie, wyżej niż RandomFileAccess ale niżej niż SQL. Bez całego tego języka którego i tak każdy unika, bez fazy planowania zapytania. Coś do dawałoby programiście bezpośredni dostęp do indeksów, coś co poprzez interfejs Stream dałoby programiście bezpośredni dostęp do danych. Ale bez możliwości parsowania wyrażeń jak ExpressionTrees w C# to może trudne albo wystawiłoby programiście średni w użyciu interfejs. Poza tym RDBMsy jednak mają swoje ficzery, jak mvcc czy planowanie zapytania ze względu na statystyki.

0

@student pro:
Zawsze możesz trzymać dane w pamięci aplikacji (w obiektach javowywch), a przy pomocy np event sourcing (jeśli go dobrze rozumiem) tylko zapisywać zmiany (wtedy składujesz zmiany, a nie cały stan systemu)

0
Shalom napisał(a):

Dopasowywanie mechanizmu persystencji pod widok to jakiś idiotyczny pomysł. Dane składuje się w logiczny dla nich sposób i tyle, niezależnie od tego czy ktoś potem chce tabelkę czy drzewko. Bo jak klient zażyczy sobie zeby mu pokazywać i drzewko ale też i tabelkę (do chce do excela skopiować) to co wtedy? :D

Składuje tak, ale prezentuje? Możesz to nazywać "dostosowywaniem mechanizmu persystencji pod widok" ale... płaska tabela to płaska tabela. Ja potrzebuję hierarchii. JSON to daje, XML to daje... płaskie tabele tego nie dają. Tracimy relacje w relacyjnej bazie danych.

Obawiam się ze nie masz pojęcia jak działają algorytmy joinowania wyników ;)

Algorytmy to jedno, ale czy baza nie może trzymać w pamięci co było z zJOINowane z czym?

0
danek napisał(a):

@student pro:
Zawsze możesz trzymać dane w pamięci aplikacji (w obiektach javowywch), a przy pomocy np event sourcing (jeśli go dobrze rozumiem) tylko zapisywać zmiany (wtedy składujesz zmiany, a nie cały stan systemu)

można ale po prostu aplikacja może połączyć się odpowiedzialnością z bazą danych, nie musi istnieć taki wyraźny podział pomiędzy bazę a aplikację, tym bardziej, że uczymy się tych wszystkich super algorytmów równoległego przetwarzania danych a suma sumarum i tak robi to za nas baza... której i tak wielu nie rozumie i nie kontroluje i maca w rękawiczkach poprzez ORMy.
Albo kesze- Postresql korzysta z mmapa więc przechodzi przez page buffer, ma też swój własny kesz aplikacyjny, no i jeszcze się keszuje hibernatem. Może jeszcze raz coś skeszować?
Są programiści którzy traktują Oracle jak serwer aplikacyjny, te światy się po prostu zderzają tak samo jak front-end w dobie SPA, wygodnie jest stosować ten sam język po obu stronach chociażby ze względu na serializację/deserializację wymienianych wiadomości.
Oczywiście pozostaje pytanie kto miałby zrobić taki porządny stos programistyczny bo na co dzień i tak trzeba używać tego co już jest na rynku.

1

można ale po prostu aplikacja może połączyć się odpowiedzialnością z bazą danych, nie musi istnieć taki wyraźny podział pomiędzy bazę a aplikację, tym bardziej, że uczymy się tych wszystkich super algorytmów równoległego przetwarzania danych a suma sumarum i tak robi to za nas baza... której i tak wielu nie rozumie i nie kontroluje i maca w rękawiczkach poprzez ORMy

i to już jest tragedia, bo nagle w bazie danych zaczyna się „dziać” logika biznesowa. Za tym nagle pojawia się właśnie taki problem „jak coś zapisać do bazy danych by była logika” (o co toczy się ta dyskusja). Baza danych służy do przechowywania danych. I do niczego więcej. Dlatego, że chcesz mieć to równoległe przetwarzanie musisz bardzo wyraźnie powiedzieć, gdzie są dane, a gdzie biznes.

Albo kesze- Postresql korzysta z mmapa więc przechodzi przez page buffer, ma też swój własny kesz aplikacyjny, no i jeszcze się keszuje hibernatem. Może jeszcze raz coś skeszować?

A teraz wyobraź sobie, że serwer bazy danych leży 30ms od ciebie. I za każdym razem musisz zapytać serwer bazy danych o jakieś podstawowe informacje, typu ostatnio dodane komentarze (w jakimś CMSie). Po co? Wielopoziomowe cache nie są robione by utrudnić komuś życie, ale po to, by było szybko, żeby nie zapychać sieci, tak na prawdę zbędnym ruchem, żeby nie obciążać dysków twardych (też mają swój cache).

Szczerze to raczej myślałem o czymś na niższym poziomie, wyżej niż RandomFileAccess ale niżej niż SQL. Bez całego tego języka którego i tak każdy unika, bez fazy planowania zapytania.

Popatrz sobie jak działa Cassandra. Utrwala dane w tylu różnych schematach, by móc później wykorzystywać konkretne schematy jako źródła danych w konkretnych zapytaniach. I nagle okazuje się, że nie potrzebujesz żadnej bazy relacyjnej, bo tam nie ma tych relacji. W zasadzie mówiąc inaczej, relacje są tworzone ad hoc w zależności od tego czy będą wykorzystywane w zapytaniach czy też nie.

0

@hcubyc: @jarekczek @jarekr000000 @Shalom

A w CQRS, w części query wg teorii powinno być jak najszybsze wyrzucenie danych z bazy read. A jeśli to jest jakieś filtrowanie, ktore zawiera jakąś logikę, którą mam napisaną w klasach to to nadal jest w części query ? Bo zmyliło mnie trochę to, że query to powinno być proste wyrzucenie prosto z repo, a u mnie to przechodzi przez jakąś warstwę logiki, która implementuje specyfikacje/predykaty. Command to niby nie jest bo nie zmieniam stanu bazy, ale no też to nie jest proste wyrzucenie.

1

IMHO nie ma problemu z tym, że po wyjęciu czegoś z bazy jakoś dodatkowo to przetwarzasz w części query, baza też ma swoje ograniczenia i nie wszystko idzie zrobić pracując z daną technologią, IMHO lepiej i wydajniej jeżeli filtrowanie może odbyć się po stronie bazy, ale jeżeli nie może to nie widzę nic złego w przetwarzaniu dtosow, które baza wypluła

0

@hcubyc:
Dzięki. W sensie logika u mnie dzieje się przed zapytaniem do bazy. Parsuję stringa i tworzę predykat do querydsl.

0

Też ok. Gdyby jednak nie było to jak miałaby wyglądać cześć query aplikacji? Ktoś po prostu by miał pytac całym zapytaniem jakie poleci pod spodem?

0

@hcubyc:

No tak, czyli tak jak myślałem.
A powiedz mi jeszcze jedną rzecz jak znajdziesz chwilkę. Czytałem artykuły Sobótki o DDD. I on tam chcę logikę apki dzielić na logikę domenową i logikę aplikacyjną. I tak jak logika domenowa jest dla mnie w pełni zrozumiała, tak aplikacyjna nie do końca. Logika aplikacyjna to są implementacje wszystkich serwisów infrastrukturalnych, db, security i cały inny stuff ? Bo tam było napisane, że do logiki aplikacyjnej wchodzą serwisy, które scalają wywołania jakiejś logiki domenowej, czyli z tego co zrozumiałem Fasady - czyli po prostu są jakimiś UseCase'ami.
No ale przecież fasada to jest wejście do modułu, a moduł to logika domenowa, więc coś mi tu nie gra.

Dlatego też jeśli np mam jakiś interfejs do puszowania notyfikacji po jakiś określonych zdarzeniach. I jego implementację robię sobie gdzieś w innej paczce z implementacjami takich serwisów, security i całymi innymi rzeczami, które podpinam do core'a apki. To gdzie taki interfejs do puszowania tych notyfikacji umieścić ? Zrobić osobny moduł Notification i tam dać jedną klasę, która jest tym interfejsem czy jak ? Bo przecież to należy do logiki aplikacji, dla testów mogę zrobić jakąś InMemory implementacje, która będzie mi wyświetlać coś na konsoli zamiast pushować serio do firebasa.

0

Anti corruption layer.
Generalnie są 2 sposoby, jeden ksiązkowy i drugi nieco leniwy. Książkowo to robisz interfejs u siebie i nazywasz to domenowo i w ogóle wow, a potem konfigurujesz gdzieś na zewnątrz domeny (DI). Bardziej leniwie to interfejs w drugim module i tak jak napisałeś możesz sobie zrobić implementację inMemory dla testów. Generalnie jeżeli to jest twój modul i twój kod infrastrukturalny to podejście drugie wydaje się OK, chyba że np. interfejs w drugim module jest bardzo rozbudowany i mówi swoim jezykiem domenowym to wtedy mozna sobie z tego wydzielic interfejs w domenie. Natomiast, jeżeli korzystasz z zewnętrznego API, które prawie zawsze mowi swoim jezykiem domenowym albo czasem żadnym to warto zrobić abstrakcje i korzystać ze swojego nazewnictwa i z tego co ci jest potrzebne w domenie, zamiast jakiegos rozdmuchanego kolosa.

0

@hcubyc:

U mnie to jest po prostu serwis, z którego będę korzystał w większości modułów, bo puszować notyfikacje będę w wielu miejscach. Tylko nie wiem czy traktować to jako domenę czy serwis aplikacyjny. Tak samo fasady. Wg Sobótki one są serwisami aplikacyjnymi bo scalają wywołania domen. Jakub Narbdalik z kolei traktuje to jako domenę.

2

Zasada jest taka, że domena powinna się kompilować jako moduł, ale jeżeli masz miec jeden interfejs z metodą 'wyslij notyfikacje' to bym tutaj sobie odpuscił. Oczywiście, jeżeli moduł zależy od innego modułu, bo jest np. ścisłe powiązanie przez fasady to już tak prosto nie będzie, ale zawsze można je rodzielić poprzez np komunikację eventami, ale to wprowadza inne ograniczenia. Ktoś tu na forum już o tym pisał, to nie jest matematyka, ze wszystko masz jasno okreslone i to jest tak i już. Zależy jakie podejście ty wybierzesz, jakie ci się sprawdza, jak dopiero próbujesz to możesz zastosować oba i po np. pół roku będziesz mógł sam ocenić, które ci bardziej odpowiada lub które ci bardziej odpowiada w danym kontekście

0

Wrócę jeszcze z pewną niejasnością, która pojawiła mi się, kiedy zacząłem stosować uproszczone CQRS.

W jaki sposób testować CommandFacade za pomocą BDD, kiedy mamy oddzieloną część command i query?

Załóżmy, że w części command w CommandFacade(wejściu do modułu) mamy metodę:

long createOrUpdate(EntityForm formToCreateOrUpdateEntity);

która coś tam przetwarza, robi bajery biznesowe na encji i zapisuje do db ustawionej na zapis. Na koniec zwraca id zapisanej encji.

W części query mamy jakiś QueryService z repozytorium podłączonym do read-db.

W jakimś tam rest controllerze flow wygląda tak:

long id = commandFacade.CreateOrUpdate(form);
return queryService.findById(id);

Dla testów piszemy sobie jakieś proste InMemory implementacje repozytoriów wykorzystując jakąś HashMapę.

W podejściu bez cqrs testowanie jest banalne, bo nie mając części query fasada zwraca od razu jakiegoś dtosa:

EntityDto dto = facade.createOrUpdateAndGet(form);

i tu mogę przetestować sobie zachowanie tego dzięki in memory prostym repozytorium.

A z CQRS tak nie zrobię, mogę jedynie przetestować czy commandFacade zwróci mi nowe albo takie samo id.

Nie mogę przetestować czy dtos zwrócony z queryServicu jest poprawny, bo InMemoryCommandRepository siedzący w CommandFacade i InMemoryQueryRepository siedzące w QueryService o sobie nie wiedzą z racji tego, że część command i query się nie widzą.

1

A czy częśc command nie może ci zwrócić obiektu domenowego, który przemapujesz na DTO? Wiem, że puryści to by chcieli żeby część command najlepiej nic nie zwracała, ale IMHO jest to niepraktyczne podejście, bo jak sam zauważyłeś jest problem z testowaniem. Generalnie dane z bazy do której piszemy trafiają później do bazy, z której czytamy, o ile to nie jest ta sama baza. Nie przypominam sobie też żeby Greg Young, który jeżeli dobrze pamiętam jest autorem CQRS miał coś przeciwko.
Ew. masz zawsze dwie zależności do CommandFacade i QueryService, ale w testach jednostkowych, żeby się nie męczyć sprawdzałbym czy zwraca ID, a w testach integracyjnych czy to faktycznie działa jak powinno

0

@hcubyc:

No racja, mogę przecież od razu zwrócić dtosa zamiast id. Ułatwia sprawę.

0

Cześć ponownie. Nie chcę zakładać nowego tematu, a w robocie naszła taka rozkmina. Jak w BDD bez Mockito przetestować metodę fasady, która w środku wywołuje jakiś zewnętrzny serwis np. do generowania jakiś PDF. Załóżmy, że nasza metoda coś tam pobiera z bazy, mieli, liczy ina końcu woła metodę do generowania tego pdfa. Jest to de facto side-effect tej fasady. Ale jak takie coś się testuje bez verify ? Tak naprawdę w tym przypadku chcę przetestować czy na końcu pdfService.generate(anyPdfDto) zostało wywołane z odpowiednio sparametryzowanym dtosem, który jest wynikiem obliczeń metody.

0

Jeśli to side effect który z pkt widzenia testu nie ma znaczenia mozesz napisać pustą 'stubową' implementacje która nic nie robi

0

Jeżeli jest to fasada, to możesz jej przekazać interfejs, który jest wykorzystywany do takiego generowania. Podstawiasz stuba i ignorujesz wyniki jego działania (zawsze zwraca poprawne).

0

@danek: @Koziołek poszedł jeszcze u mnie update wyżej.

U mnie poprawność tej metody można stwierdzić wtedy, kiedy dobrze obliczyła PdfDto, który wchodzi do serwisu przez co pdf został fizycznie wygenerowany.
Jasne jest to, że do fasady wstrzykuję PdfService. Myślałem, żeby na czas testów zrobić jakąś fejkową implementację, która coś wygeneruje jeśli dostanie dobre paramsy wejściowe, ale czy to już nie będzie mockowanie ? "Zwróć mi to, jeśli dam takie paramsy".

1

@Bambo: to teraz popatrz na swój kod. Masz tam pdfService.generate(anyPdfDto), czyli zapewne jest sobie coś takiego:

interface PdfService{
     void generate(PdfDto pdfDto);
}

Kto broni ci zaimplementować ten interfejs w nastepujący sposób:

class  PdfServiceAssertion implements PdfService{
     void generate(PdfDto pdfDto){
        Assertions.assertThat(pdfDto).isNotNull();
    }
}

Jeżeli jeszcze użyjesz AssertJ to już bajka.

0

@Koziołek:

Jak najbardziej jestem za, ale czy to nie jest mock ?

2

@Bambo, nie to jest taka cwana asercja.

0

Mam jeszcze pytanie co do samej architektury. Jestem na etapie projektowania systemu, wydzieliłem sobie moduły tak aby miały pojedynczą odpowiedzialność. W pewnym momencie doszedłem do tego, że tak naprawdę jeden z tych modułów jest corowy i agregat root tego modułu powinien zawierać agregat rooty z pozostałych modułów, bo tamte nie mogą istnieć bez tego.

Czyli na przykładzie: mam moduł A z AggregateRootA oraz moduł B z AggregateRootB i analogicznie dla C. Jeśli usuwam jakiś agregat z modułu A to tak naprawdę agregaty z B i C powinny przestać istnieć bo są spójnie połączone. Wydaje się, że są one przecież spójne i powinny być tak naprawdę jednym modułem, ale jak tak zrobię u siebie w projekcie to z 4 pakietów zrobi mi się 1 i będzie dość spory. Niby patrząc przez pryzmat DDD będzie to ok, bo wszystkie pojęcia z tych modułów, które scaliłem należą do jednego kontekstu, ale no będzie się rozrastał. Jeśli będę chciał dołożyć jakąś funkcjonalność do apki to znowu rozrośnie mi się ten 1 moduł.

Teraz mam to rozbite, ale to trochę sztucznie wygląda, bo zawsze jak chcę np usunąć agregat A, to w kodzie mam:

moduleA.delete(id);
moduleB.deleteByAgregatAId(id);
moduleC.deleteByAgregatAId(id);

Nie wiem jak to ugryźć.

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