Command Query Responsibility Segregation (CQRS) oraz Event Sourcing to ostatnimi czasy „buzz words” w branży IT. Oprócz niepodważalnych zalet płynących ze stosowania tych dwóch rozwiązań w społeczności pokutuje stwierdzenie, że sam koncept jak i implementacja są niemalże trywialne.
Czy aby na pewno? W dzisiejszym artykule postaramy się wspólnie dowiedzieć czym oba te podejścia są, jakie korzyści płyną z ich stosowania oraz jak możemy dzięki nim zaprojektować nasz system.
Zanim przejdziemy do bohatera pierwszoplanowego (CQRS), warto zapoznać się z konceptem, z którego bezpośrednio się on wywodzi. Mowa o CQS, czyli Command Query Separation. Został on przedstawiony w roku 1986 przez Bertranda Meyera. Widać więc wyraźnie, że wbrew powszechnemu stwierdzeniu nie jest to nic nowego. Czym zatem jest CQS? Jest to zasada, która mówi że każda metoda w systemie powinna być zaklasyfikowana do jednej z dwóch grup:
Bardzo spodobało mi się zdanie, które dobrze tłumaczy tą ideę:
„Pytanie nie powinno zmieniać odpowiedzi.”
Może wydawać się to dość oczywiste, jednak z mojego doświadczenia wiem, że programiści nie zawsze się do tego stosują. Bardzo prostym przykładem są metody, które wyglądają następująco:
Widać tu mały problem. Metoda GetOrCreateItem nie zawsze będzie się zachowywać identycznie. Poza tym mieszamy kod logiki biznesowej aplikacji z "głupim" pobraniem obiektu z bazy danych. Niesie to za sobą pewne problemy, o których wspomnę nieco później. Jak zapewne się domyślacie stosując CQS nasz kod w takim przypadku wyglądał by następująco:
Blisko 20 lat po "narodzinach" CQS, dwie wielkie osobistości tj. Greg Young oraz Udi Dahan przedstawili światu jego następce czyli CQRS - Command Query Responsibility Segregation. Pomysł był bardzo prosty. Dlaczego dokonujemy podziału jedynie metod na te, które pobierają dane oraz na te, które zmieniają stan naszej aplikacji? Możemy przecież zaprojektować nasz system tak, aby tymi zadaniami zajmowały się osobne klasy. To jest główna różnica między dwoma podejściami.
„Mówiąc o CQS myślimy o metodach. Mówiąc o CQRS myślimy o obiektach.”
W tym miejscu należy dodać, że nie ma jednej drogi do zaimplementowania CQRS, ponieważ tak jak w przypadku każdego wzorca projektowego podejść jest kilka. Zaraz, zaraz! Wzorca? Tak, wbrew wielu opinii, które możecie znaleźć w internecie CQRS nie jest architekturą aplikacji. To od nas zależy czy zostanie on wprowadzony "globalnie", czy jedynie w niewielkim jej fragmencie. Potwierdził to sam Greg Young:
„CQRS and Event Sourcing are not architectural styles. Service Oriented Architecture, Event Driven Architecture are examples of architectural styles.”
Dobrze, przejdźmy więc do graficznej reprezentacji ów wzorca, która powinna pomóc nam zrozumieć z czym tak na prawdę mamy do czynienia:
Tak jak wspomniałem jest to jedna z możliwych implementacji. Na schemacie widzimy wiele składowych, dlatego przejdźmy do omówienia ich po kolei:
Domyślam się, że części z Was całość mogła jeszcze bardziej zamotać w głowie, dlatego poniżej przygotowałem diagram sekwencji, który lepiej prezentuje flow danych w systemie. Przykład prezentuje realizację komendy UpdateItemQuantityCommand:
W tym wszystkim musimy jeszcze odszukać rolę Event Sourcing-u. Czy jest on składową wzorca? Nie do końca. Faktycznie, na schemacie zdarzenia wystąpiły jako sposób synchronizacji dwóch baz danych. Są one bowiem częścią CQRS, jednak takie ich zastosowanie nie realizuje założeń Event Sourcing-u. Zadaniem ES jest bowiem odtwarzanie aktualnego stanu aplikacji (patrz obiektów domenowych) na podstawie zdarzeń składowanych w magazynie danych zwanym Event Store. Z początku może wydawać się to rozwiązaniem bezsensownym jednak takim nie jest, ponieważ na podobnej zasadzie działają chociażby systemy bankowe. Prosty przykład. W jednym z moich wpisów o poziomach izolacji przytoczyłem przykład ilustrujący zjawisko false update (jeżeli ktoś nie czytał niech zrobi pauzę i zapozna się z tym fragmentem na moim blogu www.foreverframe.net). Zauważcie co spowodowało tam błąd składowanych danych. System przechowywał jedynie jedną liczbę, która informowała użytkownika o jego saldzie. Gdyby zamiast tego generowane było zdarzenie mówiące o zwiększeniu salda o konkretną kwotę, problem by nie istniał. Dlaczego? Ponieważ zamiast jednej informacji o saldzie, moglibyśmy aplikować w naszym obiekcie domenowym eventy, które stopniowo doprowadziłby nas do aktualnego stanu konta użytkownika. Ta prosta koncepcja jest zatem bardzo potężna w swym działaniu. Jak to się ma zatem do naszego aktualnego schematu wzorca CQRS? Jedyną zmianą byłoby zastąpienie Write DB magazynem Event Store. Drugą zmianą byłby sposób pobieranie obiektów domenowych. Zamiast pobierać je "w całości" teraz należy pobrać wszystkie zdarzenia, które wygenerował dany domain object, a następnie w jego wnętrzu je aplikować, tym samym otrzymując jego stan obecny. Proste? A jakie przydatne!
Wiemy zatem jak powinniśmy podejść do implementacji CQRS/ES. Nie znamy jeszcze odpowiedzi na kluczowe pytanie: po co? Klasyczne aplikacje N-Layer wydają się przy tym bardzo proste i nie komplikują systemu do tego stopnia. Przygotowałem dla Was kilka powodów, które być może przekonają Was do tego konceptu:
Jeżeli zainteresował Cię ten temat zajrzyj na mojego bloga www.foreverframe.net.
Dariusz Pawlukiewicz - Programista pasjonat. Za sprawą konkursu „Daj się poznać” świeżo upieczony blogger oraz podcaster. Uwielbia poznawać nowe, ciekawe technologie i nie boi się używać ich w swoich projektach. Fan TypeScript oraz frameworku Aurelia. Entuzjasta DDD, CQRS i Event Sourcingu. Na co dzień pracuje, jako Full Stack Developer w Connectis_.
Chcesz rozwijać się w Connectis_? Zobacz nasze aktualne OFERTY PRACY. :)