CQRS nauka

0

Mediator powinieneś używać, kiedy występuje sieć połączeń pomiędzy wieloma obiektami, które wywołują się na krzyż i tp. Po co ci on w twoim przypadku skoro i tak kontroler będzie wołał prawdopodobnie jeden lub dwa serwisy, no chyba że rzeczywiście każda klasa to inny handler dla innej komendy(SRP....heh), no bo zrobić jeden serwis spójny pojęciowo z normalnymi metodami to za łatwo. To i tak nie rozumiem po co ci mediator skoro ta komunikacja występuje po linii prostej Komenda-->Handler.? Przecież warstwę prezentacji piszesz pod konkretną logikę, zmieniasz logikę biznesową zmieniasz prezentację. Wyjaśnij mi co takiego może się zmienić w prezentacji co może w jakiś sposób przeszkadzać logice biznesowej? Jeśli nic to po co ci te pseudo odwracanie zależności? Jak i te pseudo proceduralne SRP.

Może jak nie ty, to ktoś inny mi to wyjaśni?

1
john_klamka napisał(a):

A co do ARów, nigdzie Evans nie mówi, że repozytorium może zwracać tylko ARy, natomiast wyraźnie mówi, że repozytorium realizuje dostęp do encji.

Nie wiem co mówi Evans, bo nigdy z nim nie gadałem, ale w swojej książce napisał:

A REPOSITORY represents all objects of a certain type as a conceptual set (usually simulated). It acts like a
collection, except with more elaborate querying capability. Objects of the appropriate type are added and
removed, and the machinery behind the REPOSITORY inserts them or deletes them from the database. This
definition gathers a cohesive set of responsibilities for providing access to the roots of AGGREGATES from
early-lifecycle through the end.

I dalej:

Only provide repositories for AGGREGATE
roots that actually need direct access. Keep the client focused on the model, delegating all object
storage and access to the REPOSITORIES.


neves napisał(a):

Mediator użyty do rozprowadzania komend może się przydać do implementacji jakiś cross cutting concerns, jak np logowania jakie komendy są wywoływane. Ale jeśli nie mamy żadnych ccc to równie dobrze można się objeść bez mediatora i bezpośrednio wywoływać command handlery z poziomu kontrolerów, które jakby nie były są zwyczajnymi serwisami aplikacyjnymi tylko z inną nazwą ;)

Tak, możemy się obejść - tak jak to robiliśmy w latach osiemdziesiątych. Tylko ja wtedy jeszcze nie programowałem, więc nie jestem jakimś wielkim fanem tamtego stylu, ani kultów cargo w ogólności. Przez osiem lat pisania w ten sposób udało mi się zebrać następujące spostrzeżenia:

  1. Jeśli mamy kontroler, który jest nakładką na jakiś application service i ma metody 1:1 wystawiające metody serwisu, to po prostu wygląda, jakby któraś z tych klas była tylko zbędnym, głupim wrapperem.
  2. Sztywne wiązanie utrudnia życie. Jeśli okaże się, że kontroler musimy korzystać z wielu serwisów, to trzeba dodawać nową referencję i nowe pole. Przeniesienie/skopiowanie jakiegoś ficzera do innego kontrolera - to samo. Im więcej zależności tym boleśniejsza refaktoryzacja.
  3. Czystość i higiena kodu - w małej klasie z jedną metodą nawet seniorowi trudno będzie złamać SRP. A przede wszystkim testy będą ładniejsze, bo gdy mamy god-serwisy, to praktycznie zawsze kończy się tak, że jeden test suit testuje wszystkie metody takiego serwisu. Efektem jest przeraźliwie długa klasa testowa ze skomplikowanym setupem. Klasy testujące handlery są krótsze i prostsze.
  4. Brudzi przy podejściu generycznym. Np. jeśli mamy generyczny serwis wystawiający dajmy na to wszystkie metody crudowe, a w kontrolerze nie potrzebujemy niektórych z nich.

Wad podejścia z mediatorem nie stwierdziłem, ale chętnie poznam choćby jedną.

0

Jeśli mamy kontroler, który jest nakładką na jakiś application service i ma metody 1:1 wystawiające metody serwisu, to po prostu wygląda, jakby któraś z tych klas była tylko zbędnym, głupim wrapperem.

Ale kontroler ma być głupim wrapem, poza tym co mediator zmienia na tej płaszczyźnie.?

Sztywne wiązanie utrudnia życie. Jeśli okaże się, że kontroler musimy korzystać z wielu serwisów, to trzeba dodawać nową referencję i nowe pole. Przeniesienie/skopiowanie jakiegoś ficzera do innego kontrolera - to samo. Im więcej zależności tym boleśniejsza refaktoryzacja.

A to prawda te dwie rzeczy, są okropnie uciążliwe i zmniejszają czytelność kodu. Lepiej jest szukać w gąszczu klas nazwę pasującą do komendy.

Czystość i higiena kodu - w małej klasie z jedną metodą nawet seniorowi trudno będzie złamać SRP. A przede wszystkim testy będą ładniejsze, bo gdy mamy god-serwisy, to praktycznie zawsze kończy się tak, że jeden test suit testuje wszystkie metody takiego serwisu. Efektem jest przeraźliwie długa klasa testowa ze skomplikowanym setupem. Klasy testujące handlery są krótsze i prostsze.

To, że ktoś pisze testy pod klasę zamiast ficzer albo co gorsza jest seniorem. Nie ma nic wspólnego z dedykowanym handlerem, czy mediatorem.

Brudzi przy podejściu generycznym. Np. jeśli mamy generyczny serwis wystawiający dajmy na to wszystkie metody crudowe, a w kontrolerze nie potrzebujemy niektórych z nich.

No to wtedy ich nie używamy...?

Jak nie stwierdzasz, żadnych wad to może przedstawisz jakieś zalety?

0

Im więcej zależności tym boleśniejsza refaktoryzacja.

O jakiej refaktoryzacji mówisz ? o refaktoryzacji metody w kontrolerze?, a to ciekawe. To nie wystarczy podmienić tylko metodę obiektu, który odbiera request?

Przecież i tak musisz mieć zależność do komendy i requestu więc co masz za chory problem, jeśli w tym samym namespace będzie serwis czy ten twój handler.

Bez sensu, Przecież mediator ma izolować komunikację a nie referencje, co ci ta referencja tam przeszkadza?.

0

Minus jest taki, że ten mediator jest niepotrzebny i nie wnosi żadnej wartości do projektu. No, chyba że jesteś uparty na dedykowane handlery. Ponieważ przewidujesz późniejsze skalowanie tych handlerów ale po co w ten sposób zaczynać pisać aplikację?. Nie wiem.

1
somekind napisał(a):

Nie wiem co mówi Evans, bo nigdy z nim nie gadałem (...)

Za słownikiem języka polskiego PWN:
mówić:

  1. «posługiwać się słowami w celu komunikowania myśli i przeżyć lub przekazywania informacji»
  2. «posługiwać się jakimś językiem»
  3. «zwracać się do kogoś w jakiejś formie»
  4. «informować, podawać do wiadomości, zawierać jakąś treść»
  5. «wypowiadać jakiś tekst, zwykle na głos»
  6. «wyrażać coś, coś znaczyć, świadczyć o czymś»

Czepianie się słówek sprowadza dyskusję do parteru, nie rób tego, proszę.

A REPOSITORY represents all objects of a certain type as a conceptual set (usually simulated). It acts like a
collection, except with more elaborate querying capability. Objects of the appropriate type are added and
removed, and the machinery behind the REPOSITORY inserts them or deletes them from the database. This
definition gathers a cohesive set of responsibilities for providing access to the roots of AGGREGATES from
early-lifecycle through the end.

I dalej:

Only provide repositories for AGGREGATE
roots that actually need direct access. Keep the client focused on the model, delegating all object
storage and access to the REPOSITORIES.

Oczywiście w idealnym świecie tak jest, natomiast w rzeczywistych projektach nie da się zrealizować wszystkich wytycznych Evansa - np. z powodu ograniczeń technologicznych. Zresztą sam wiele razy wspominał o tym, że DDD przede wszystkim ma służyć zaprojektowaniu systemu, który przynosi wartość dla klienta.

W niebieskiej książce jest poza tym napisane:
"Even a REPOSITORY design with flexible queries should allow for the addition of specialized hardcoded
queries. They might be convenience methods that encapsulate an often-used query or a
query that doesn't return the objects themselves, such as a mathematical summary of selected
objects."
i chociaż tu jest mowa tylko o metodach w repozytorium, to sam widzisz, że Evans dopuszcza pewne kompromisy w systemie.

A tak w ramach ciekawostki, spotkałem się z repozytorium zwracającym DTO mapujące pewien AR do użycia w w http api z pominięciem serwisu aplikacji - i w kontekście tego konkretnego projektu miało to sens.

Jeśli zaś mówimy o repozytorium oderwanym całkowicie od pozostałych wzorców taktycznych, czyli typowym "entity framework repository pattern tutorial", to zgadzam się z Tobą w pełni i uważam, że powinno się za to palić na stosie ;)

1

Programuje od prawie dwóch lat i staram się łapać tematy jak wzorce, DDD, CQRS czy inne. Głównie siedzę w WebApi (tak .NET). I... Czytając ten wątek wywracane jest praktycznie wszystko co "uczą" w książkach (przynajmniej tych podstawowych).

somekind napisał(a):
Jeśli mamy kontroler, który jest nakładką na jakiś application service i ma metody 1:1 wystawiające metody serwisu, to po prostu wygląda, jakby któraś z tych klas była tylko zbędnym, głupim wrapperem.

Dokładnie tak jest! Moje metody WebApi w większości odzwierciedlają metody z serwisów. Czasami metoda kontrolera wywoła dwie z serwisów.

[Route("api/users")]
IHttpActionResult Get() => Ok(this._usersDao.GetAll());

Tak to wygląda.

Jakiś czas temu wprowadziłem mediatora i jedna metoda WebApi wysyłała jedną komendę lub query do mediatora.
Koniec końców wciąż było tak, że metody kontrolera tylko przekazywały coś niżej.
Rozumiem, że największy plus tego to fakt, iż kontrolery WebApi nie wiedziały nic o Dao?

[Route("api/users")]
IHttpActionResult Get(GetAllUsersQuery query) => Ok(this.mediator.Execute(query)); // I to jest w porządku?
0
Nadziany Szczur napisał(a):

Jakiś czas temu wprowadziłem mediatora i jedna metoda WebApi wysyłała jedną komendę lub query do mediatora.
Koniec końców wciąż było tak, że metody kontrolera tylko przekazywały coś niżej.
Rozumiem, że największy plus tego to fakt, iż kontrolery WebApi nie wiedziały nic o Dao?

Tak, poza tym kod staje się wtedy bardziej ekspresywny. Kontroler nie wie jak coś jest wykonane, wysyła tylko polecenie i zwraca wynik. Poza tym tak jak było wspomniane- kontrolera nie zasmieca się serwisami, a handler dostaje tylko te serwisy które są potrzebne do wykonania konkretnej operacji.

0
Nadziany Szczur napisał(a):

Programuje od prawie dwóch lat i staram się łapać tematy jak wzorce, DDD, CQRS czy inne. Głównie siedzę w WebApi (tak .NET). I... Czytając ten wątek wywracane jest praktycznie wszystko co "uczą" w książkach (przynajmniej tych podstawowych).

somekind napisał(a):
Jeśli mamy kontroler, który jest nakładką na jakiś application service i ma metody 1:1 wystawiające metody serwisu, to po prostu wygląda, jakby któraś z tych klas była tylko zbędnym, głupim wrapperem.

Dokładnie tak jest! Moje metody WebApi w większości odzwierciedlają metody z serwisów. Czasami metoda kontrolera wywoła dwie z serwisów.

[Route("api/users")]
IHttpActionResult Get() => Ok(this._usersDao.GetAll());

Tak to wygląda.

Jakiś czas temu wprowadziłem mediatora i jedna metoda WebApi wysyłała jedną komendę lub query do mediatora.
Koniec końców wciąż było tak, że metody kontrolera tylko przekazywały coś niżej.
Rozumiem, że największy plus tego to fakt, iż kontrolery WebApi nie wiedziały nic o Dao?

[Route("api/users")]
IHttpActionResult Get(GetAllUsersQuery query) => Ok(this.mediator.Execute(query)); // I to jest w porządku?

Bo tak mówi Scott Allen, który, również promuje, crudowe generyczne repository z EF. Oraz jakieś chore kombinacje typu repository w Unit Of Work. Którego fanem jest Aventus

Ale co ty tam wiesz. Nawet jak możesz wstrzyknąć jeden serwis to masz używać mediatora, bo bogowie tego forum mówią, że tak ma być i koniec. Nie wypowiadaj się, bo cię zbanują albo wyśmieją.

Czepianie się słówek sprowadza dyskusję do parteru, nie rób tego, proszę.

Ja już jakoś się tym nie przejmuje.

Aventus napisał(a):

Tak, poza tym kod staje się wtedy bardziej ekspresywny. Kontroler nie wie jak coś jest wykonane, wysyła tylko polecenie i zwraca wynik. Poza tym tak jak było wspomniane- kontrolera nie zasmieca się serwisami, a handler dostaje tylko te serwisy które są potrzebne do wykonania konkretnej operacji.

To ile jest tych serwisów w handlarze, 3 - 5 a może 10 ? Daruj sobie, szkoda tego słuchać.... Lepiej pójść pokopać piłkę albo pozjeżdżać na poręczy od klatki schodowej.

Tak, poza tym kod staje się wtedy bardziej ekspresywny. Kontroler nie wie jak coś jest wykonane, wysyła tylko polecenie i zwraca wynik

Dokładnie to samo uzyskujesz bez mediatora z serwisem. No i...?

0
john_klamka napisał(a):

Czepianie się słówek sprowadza dyskusję do parteru, nie rób tego, proszę.

Wiesz, może to był suchar, ale bez przesady. Można chyba sobie trochę pożartować nawet dyskutując na poważne tematy. ;)

Oczywiście w idealnym świecie tak jest, natomiast w rzeczywistych projektach nie da się zrealizować wszystkich wytycznych Evansa - np. z powodu ograniczeń technologicznych. Zresztą sam wiele razy wspominał o tym, że DDD przede wszystkim ma służyć zaprojektowaniu systemu, który przynosi wartość dla klienta.

Ja po prostu jestem za tym, żeby który nie jest robiony z duchem DDD nie stosował tamtejszej nomenklatury. Udawanie w ten sposób profesjonalizmu sprawa jedynie, że wychodzi się na ignoranta i laika.

W niebieskiej książce jest poza tym napisane:
"Even a REPOSITORY design with flexible queries should allow for the addition of specialized hardcoded
queries. They might be convenience methods that encapsulate an often-used query or a
query that doesn't return the objects themselves, such as a mathematical summary of selected
objects."
i chociaż tu jest mowa tylko o metodach w repozytorium, to sam widzisz, że Evans dopuszcza pewne kompromisy w systemie.

Ale gdzie tu kompromis? Tu jest mowa o dwóch podejściach do implementacji w zależności od potrzeb.

A tak w ramach ciekawostki, spotkałem się z repozytorium zwracającym DTO mapujące pewien AR do użycia w w http api z pominięciem serwisu aplikacji - i w kontekście tego konkretnego projektu miało to sens.
Jeśli zaś mówimy o repozytorium oderwanym całkowicie od pozostałych wzorców taktycznych, czyli typowym "entity framework repository pattern tutorial", to zgadzam się z Tobą w pełni i uważam, że powinno się za to palić na stosie ;)

No, a czym takie "repozytorium" z tutoriala rózni się od "repozytorium" zwracającego DTO dla API? Dodanie sufiksu "repository" do nazwy klasy nie czyni z niej repozotorium, i tak jest w obu tych sytuacjach.
Można sobie wstawić kuchenkę gazową oraz lodówkę do łazienki i nazwać takie pomieszczenie "kuchnią", ale to nie zmieni faktu, że tam nadal jest wanna, kibel i jest to łazienka.

Nadziany Szczur napisał(a):

Programuje od prawie dwóch lat i staram się łapać tematy jak wzorce, DDD, CQRS czy inne. Głównie siedzę w WebApi (tak .NET). I... Czytając ten wątek wywracane jest praktycznie wszystko co "uczą" w książkach (przynajmniej tych podstawowych).

Jeśli masz na myśli książki np. Freemana odnoście programowania MVC, z repozytoriami wstrzykiwanymi do kontrolerów, które to realizują logikę biznesową to owszem, tam jest pokazywane niepoprawne podejście - w rzeczywistości ani to MVC, ani to repozytoria.

0

Jeśli masz na myśli książki np. Freemana odnoście programowania MVC, z repozytoriami wstrzykiwanymi do kontrolerów, które to realizują logikę biznesową to owszem, tam jest pokazywane niepoprawne podejście - w rzeczywistości ani to MVC, ani to repozytoria.

Mnie się wydaje, że mu chodzi o mediatora oraz proceduralny styl programowania, gdzie obiekt równa się procedura. Którego, jesteś fanem.

1

Tak się podepnę pod temat - a co myślicie o używaniu QueryObjectów, bo też widziałem, że niektórzy pokazują implementacje przy użyciu interfejsów w stylu IQuery<TResult>, IQueryHandler<TQuery>, a inni że to be i QueryObjecty niepotrzebne.

Żeby nie tworzyć bezsensownych wrapperów na ORMowe repo myślałem, żeby zrobić generycznego CRUDa, który pod spodem korzysta bezpośrednio np. z DbSeta, a jak potrzebuję bardziej specyficzne query to zrobię sobie query objecty.
Wydawało mi się to w miarę ok podejście, bo:

  1. nie mam bezsensownych wrapperów na ORMa, które w dłuższej mierze powodują tylko problemy np. wystawianie IQueryable, czy Specification z takiego repo,
  2. jak w kilku miejscach potrzebuję zrobić to samo query to po prostu używam tego samego query objecta (widziałem projekty, gdzie generyczne repo zwracało IQueryable, a jak później w kilku miejscach trzeba było zrobić takie samo query to od nowa było składane wyrażenie LINQ na 15 linii kodu z zagnieżdżonymi Selectami :|),
  3. 1 klasa = 1 query wydawało mi się lepszym podejściem, niż repo z 10 metodami (GetXXXWithYYYWithoutZZZHavingAAA)

Ale jak zwykle(jak chyba przy każdym wzorcu) na 5 opinii każda jest inna :D

1

Taaaaa tu się to ostatnio pojawiło https://geek.justjoin.it/command-query-responsibility-segregation-pierwsze-kroki/, i jeszcze to przez mediatora przepuszczają tak jak komendy :D. Mój stosunek jest taki sam jak do komend, jeśli potrzebujemy jakieś programowanie aspektowe/cross cutting concerns w okół przetwarzania komend/zapytań to można to przepuszczać przez mediatora. Jeśli nie mamy żadnego aop/ccc to nie ma potrzeby używania mediatora.

Natomiast zawsze jest dobrze mieć ten kod oddzielony od infrasturktury/kontrolerów, bez różnicy czy się to nazwie i opakuje QueryHandlerem, DAO, Repository czy jakimś Servicem.

0
neves napisał(a):

Taaaaa tu się to ostatnio pojawiło https://geek.justjoin.it/command-query-responsibility-segregation-pierwsze-kroki/, i jeszcze to przez mediatora przepuszczają tak jak komendy :D. Mój stosunek jest taki sam jak do komend, jeśli potrzebujemy jakieś programowanie aspektowe/cross cutting concerns w okół przetwarzania komend/zapytań to można to przepuszczać przez mediatora. Jeśli nie mamy żadnego aop/ccc to nie ma potrzeby używania mediatora.

Natomiast zawsze jest dobrze mieć ten kod oddzielony od infrasturktury/kontrolerów, bez różnicy czy się to nazwie i opakuje QueryHandlerem, DAO, Repository czy jakimś Servicem.

Nie musisz, wystarczy, że w kontenerze IOC zaimplementujesz sobie interoceptor albo dekorator, jeśli to ci bardziej pasuje. Kontener w tym kontekście jest o wiele bardziej elastyczny.

Mój stosunek jest taki, że mediator ma pełnić funkcje fasady z naciskiem na izolowanie tego, jak coś się komunikuje.

I nie jest żadną dobrą praktyki w stylu "Controller on diet with mediator".
A praktyki, typu rozdzielanie obiektów na:

new CreateproductHandlerComand.Handle(command)
new ChangeProductPriceHandlerComand.Handle(command)

Zamiast

new Product(Id, Price);
Product.ChangePrice(command);

// or

new ProductSerwice();
Product.CreateProduct(command);
Product.ChangePrice(command);

Jako że dwa obiekty są zgodne z SRP, A jeden nie.
Jest błędem w rozumowaniu na poziomie logiki.

Jak i podobne tego typu bzdety jak łatwiejsze testowanie, lepsza struktura projektu (Chociaż sam autor bloszka trzyma komendy w domenie, a to już jest znak, że coś się dzieje....) i tp.

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