fbpx
devstyle.pl - Blog dla każdego programisty
devstyle.pl - Blog dla każdego programisty
4 minut

Esencja CQRS – to bardzo proste


23.11.2016

CQRS (Command Query Responsibility Segregation) ma wiele odmian. Jeden napisze tak, drugi napisze inaczej. I FIGHT – święta wojna gotowa. O podstawach CQRS, z odrobiną historii i przykładami, wkrótce jeszcze napiszę. Dziś: o esencji, o “corze”, o serduszku <3.

Akcja: BLOGvember! Post nr 18.
W listopadzie w każdy roboczy poranek na devstyle.pl znajdziesz nowy, świeżutki tekst. W sam raz do porannej kawy na dobry początek dnia.
Miłej lektury i do przeczytania jutro! :)

O co spór?

Różnice w implementacji podejścia CQRS zaobserwować można na każdym kroku. Każdy programista ma swoje racje i nie ma jednej poprawnej drogi. Jak to zwykle bywa: “whatever works“. Z dopiskiem: “in a given context“.

Niedawno zaprezentowałem na blogu swoje podejście do składowych komponentów CQRS i ich rejestracji w kontenerze DI. Niedługo pojawi się kolejny, dłuższy, tekst na podobny temat. Można tam poczytać o takim tworze jak ICommandBus – komponencie, którego rolą jest dystrybucja wiadomości w danym systemie. Dla mnie to piękna koncepcja, niosąca bardzo wiele korzyści i praktycznie pozbawiona wad. Znajdą się jednak zwolennicy teorii, że czegoś takiego jak “szyna komend” w ogóle nie powinno się implementować. I git, nie ma się o co bić.

W moich projektach, na moje potrzeby, w MOIM KONTEKŚCIE, zrównoleglanie operacji najczęściej nie było potrzebne. Wyznaję zasadę, aby wszystko było tak proste jak to tylko możliwe. Jeśli da się więc osiągnąć założenia biznesowe bez wielowątkowości, komunikacji między procesami czy asynchroniczności, to tak właśnie zrobię. Można jednak natknąć się stwierdzenia, że “implementowanie CQRS na jednym wątku jest bez sensu“. Hmm… U mnie tak to właśnie działało: komenda -> handler -> event -> handler -> event -> handler – wszystko na jednym wątku, bez zrównoleglania. I sprawdziło się wyśmienicie.

Wreszcie: “jakie dane powinny zwracać komendy?“. W MOIM podejściu: żadnych (o tym też się jeszcze rozpiszę). Komenda to VOID, i już. Ileż razy byłem jednak przekonywany, że to bardzo ograniczające i nieprawidłowe podejście…

Wiesz co? Wszystkie powyższe spory to tylko szczegóły implementacyjne.

Esencja CQRS

Samo serce CQRS to jedno proste przykazanie: oddziel zapis od odczytu. Tylko tyle i aż tyle.

CQRS = oddzielenie zapisu od odczytu

Jeśli masz problemy opisane powyżej, to jest już dobrze. To są problemy, które fajnie jest mieć. Przynajmniej w porównaniu z alternatywą.

W CQRS bierzemy to:

clip_image001

Czyli JEDEN MODEL, służący do wszystkiego. I do zapytań, i do wykonywania logiki, i do reprezentacji danych. Duży zestaw skomplikowanych klas. Zestaw, który się nie sprawdza.

Zamieniamy to na:

clip_image002

Uświadamiamy sobie, że potrzebujemy więcej niż jeden model. Potrzebujemy przynajmniej dwa modele: jeden przystosowany do odczytu danych i jeden dedykowany do przetwarzania/zapisu.

Jak je zaimplementujemy? To kwestia drugorzędna. Ja mam swój sposób, Ty możesz mieć inny sposób. Po takiej operacji i tak jesteśmy w o wiele lepszej pozycji, niż jeszcze niedawno.

Bo:

Asking a question should not change the answer
Bertrand Meyer

BUM. Koniec.

Bonus 1

O CQRS możesz posłuchać w moim podkaście nagranym z jednym z dwóch “ojców” tego podejścia: DevTalk#14 – CQRS with Udi Dahan. Bardzo ciekawa audycja w kontekście mniej technicznym, takie treści trudno znaleźć więc bardzo polecam.

Bonus 2

Dlaczego ten obrazek pasuje do dywagacji o CQRS? :)

clip_image003

0 0 votes
Article Rating
26 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
maciek
7 years ago

Policjantom się nie spodobało, bo to wyjaśniłeś za szybko! W CQRS ma być powoli, trudno i skomplikowanie! Panów policjantów nie interesuje czy dowieziesz paczke do klienta, panów policjantów interesuje czy twoja komenda zwraca void.

A tyś Pan to na dziecięcej zabawce rozrysował:)

Piotrek
7 years ago
Reply to  maciek

Policjanci zawsze chodzą parami, bo jeden umie czytać a drugi pisać ;)

maciek
7 years ago
Reply to  Piotrek

A to też, też:)

jedmac
7 years ago

Bo jeden czyta, a drugi pisze :P

Dariusz Pawlukiewicz
7 years ago

Ja bym jeszcze dodał w ramach tej esencji, że implikacją takiego podziału jest możliwość skalowania asymetrycznego aplikacji oraz doboru technologii/struktur danych pod operacje zapisu i odczytu. Zgadzam się też, że trwa od dawna wojna o “poprawną” implementację tego wzorca. “ooooo aaaa CommandBus to antywzorzec!”, “lol, CommandHandler zwraca VOID? To jak pobiore ID nowo utworzonego elementu? to bez sensu i tylko komplikujesz sobie życie”. Najlepsze jest, że potem wiele osób w pewnym momencie nieświadomie zaczyna implementować CQRS wydzielają komponenty pod zapis i odczyt, ponieważ jest to wygodne i zazwyczaj nie ma problemu ze znalezieniem tej granicy pomiędzy read i write.

Anyway, dobry tekst i czekam na kolejną część :)

devamator
devamator
7 years ago

W temacie ID można by się pewnie sporo rozpisywać, ale dla mnie najważniejszym aspektem jest, że ID to dana czysto techniczna.
Warstwa dostępu do danych jak najbardziej z niej korzysta i tam IDiki latają tam i z powrotem.
Według mnie CQRS w swoim założeniu lata pomiędzy warstwą interfejsu (UI,WCF,Bus,itp…), a na tej warstwie powinny latać identyfikatory bardziej biznesowe (np.: numer faktury a nie jej id).
A skoro do Command trafił na wejściu identyfikator biznesowy, to po co go przesyłać dalej, skoro w kontekście wywołania swojej komendy już go masz.

RG
RG
7 years ago

Było by łatwiej gdyby nie to że ktoś kiedyś wymyślił formularze html-owe które standardowo robią post-a na ten sam adres z którego przyszły – czyli wyświetlasz stronę z formularzem no więc ta strona powinna też obsługiwać zapis danych z tego formularza. No i jakoś mnóstwo ludzi się do tego przywiązało bo wygodne i intuicyjne. No i jak tu przylepić do tego CQRS bez mieszania wszystkiego ze wszystkim?

Dariusz Pawlukiewicz
7 years ago
Reply to  RG

No, ale GET i POST to na serwerze dwie oddzielne akcje kontrolera. CQRS w zasadzie nie dotyczy warstwy HTTP, tylko wszystkiego co poniżej. W związku z tym Twój GET w kontrolerze będzie wołał read model z części aplikacji do odczytu, natomiast POST będzie przekazywał command dalej do ComamndBusa czy bezpośrednio do CommandHandlera. Nie ma tu żadnego problemu.

RG
RG
7 years ago

Tyle że w tak oczywistym miejscu musisz CQRSa wepchnąć gdzieś w niższe warstwy, bo Twój MVC kontroler obsługuje zarówno odczyt jak i zapis w GUI (a często jeden POST jednocześnie zapisuje dane i generuje widok w HTMLu). Czyli jak na moje oko oczywista skucha tego obiecującego modelu – zamiast rozwiązać problem omijamy go przez wrapowanie, abstrakcję i schowanie gdzieś głębiej.

RG
RG
7 years ago

“Whatever works” – każdy stosuje, w końcu chodzi o to żeby było łatwo i prosto, a jak się przy okazji da dobrze to super.
I raczej wszystko jest OK z kontrolerem bo to standardowa praktyka żeby w jednym requeście POST zapisać dane a potem wygenerować view. Bo po co robić dwa requesty do serwera jeśli można jeden?. Ale to też oznacza że zapis i odczyt następują w jednej transakcji więc separacja jednego od drugiego jest tylko logiczna – można sobie oddzielić kod z tych obszarów i MVC to robi. Nadal jednak view musi czekać aż dane zostaną zapisane.
tak więc czuję się nie do końca usatysfakcjonowany odpowiedzią że standardowa obsługa formularzy/POSTów jest niedobra ale jak chcesz mieć dobrą w CQRS to musisz sobie jakoś inaczej poradzić. No właśnie, jak dokładnie, i jak zrobić żeby nie było to trudniejsze niż nie stosowanie CQRSa?

RG
RG
7 years ago

czyżby początki świętej wojny? :) Myślę że obsługa transakcji to ważny temat i nie da się go obejść ani zignorować – w końcu wyjdzie i okaże się że ta separacja odczytu od zapisu to coś trudniejszego niż tylko organizacja kodu, a jeśli oddzielisz od siebie te operacje do niezależnych transakcji to napotkasz problemy ze współbieżnością / synchronizacją operacji.
Co do Post/Redirect/Get – to już ma znamiona dogmatu, w końcu niezależnie od typu requestu wysyłasz dokładnie ten sam pakiet danych. To nie dotyka istoty problemu i samo majstrowanie przy urlach go nie usuwa.
PS nie żebym się czepiał, mam jakieś swoje metody ‘CQRS-like’ które dobrze sobie radzą z posprzątaniem bajzlu wprowadzanego przez ‘modele’ w MVC, ale byłem ciekaw jakichś głębszych wniosków.

devamator
devamator
7 years ago

Widzę, że pożyczyłeś profesjonalny tablet do rysowania diagramów :)

A co do wpisu, to CQRS jest jednym z wielu możliwych podejść zapewniających SOLID na poziomie architektury projektu.

devamator
devamator
7 years ago

Maciek,
Masz rację, żadne rozwiązanie nie gwarantuje że zostanie zachowany solid. To czy kod będzie spełniał te wymogi czy nie to już tylko dobra wola programisty :)
Jak widać mój myślowy skrót nie jest tak jednoznaczny jak mi się wydawało w trakcie pisania.
Jednak według mnie, tak jak interfejsy ułatwiają testowanie, tak cqrs ułatwia trzymanie się tych zasad, a przynajmniej zauważyłem, że łatwiej programistom je spełnić.
Zespół z którym pracuję jakiś czas temu stwierdził (i to niemal jednogłośnie), że to podejście ułatwia zrozumienie całości (dziel i rządź), testy biznesowe oraz dalsze utrzymanie projektu (poprawki, zmiany) nawet w obszarach, których się wcześniej nie dotykało. A zmiany powodują wymiernie miej dziwnych zachować w innych częściach systemu.

PaSkol
7 years ago

Poczekam aż dojdziesz (he, he :) ) do kwestii komend i ich uchwytu. Być może wdam się w polemikę.

MaciejBurchardt
MaciejBurchardt
7 years ago

Co do Bonusu numer 2 -> Obrazek pasuje bo nie pisze się wpisów na bloga o CQRS prowadząc samochód? ;)

Keraxel
7 years ago

Kiedyś zrobię sobie tatuaż. Na jednym przedramieniu będzie “whatever works”, a na drugim “in a given context”. Dzięki za inspirację!

Kurs Gita

Zaawansowany frontend

Szkolenie z Testów

Szkolenie z baz danych

Książka

Zobacz również