You are on page 1of 27

CQRS

Pragmatycznie

Maciej Aniserowicz
devstyle.pl
CQRS pragmatycznie

Nowoczesne podejcie do tworzenia systemw w praktyce


CQRS Command Query Responsibility Segregation to bardzo popularny
temat wrd programistw od adnych kilku lat. Wok niego uroso tyle
dodatkowych poj i zalenoci, e aktualnie ciko jest zacz zgbia to
zagadnienie. W artykule przedstawi czym naprawd jest CQRS i jak to
ugry. Przykady kodu napisane s w C#, jednak powinny by zrozumiae dla
programistw kadego jzyka.

Troch historii
Ojcami CQRS s Udi Dahan i Greg Young wymienici eksperci wywodzcy
si ze wiatka .NET. Pod koniec pierwszej dekady XXI wieku okrelili kilka
prostych zasad, ktre potrafi znacznie uprzyjemni prac nad
skomplikowanymi systemami informatycznymi. Mona wspomnie, e wwczas
wyszukanie informacji na ten temat nie byo proste:

Rysunek 1. Wyszukiwanie materiaw o CQRS na pocztku biecej dekady.

Wbrew pozorom jednak, w dniu dzisiejszym z CQRS powizane jest bardzo


wiele innych poj. Mam na myli chociaby Event Sourcing, Doman Driven
Design, Multiple datastores, NoSQL databases czy Service-Oriented
Architecture. Warto zdawa sobie jednak spraw z faktu, i zastosowanie
CQRS wcale nie wymaga znajomoci tych poj!

CQRS pragmatycznie | devstyle.pl


CQRS pragmatycznie
U podstaw CQRS ley zasada CQS (Command Query Separation)
przedstawiona przez Bertranda Meyera wybitn posta w IT, twrc jzyka
Eiffel jeszcze w latach 80 poprzedniego stulecia. Zasada ta ma jedno
zaoenie: zadanie pytania nie powinno zmienia odpowiedzi (asking a
question should not change the answer). Przekadajc to na jzyk
programistw: kada metoda powinna albo wykonywa operacje modyfikujce
stan systemu albo zwraca dane, nigdy jedno i drugie jednoczenie.

Przedstawieni wczeniej panowie rozszerzyli nieco t zasad, zaktualizowali j


do najnowszych trendw, jzykw i technologii oraz zarekomendowali jej
stosowanie do komponentw wikszych ni metoda. Tak narodzio si CQRS.

CQRS.init()
Command Query Responsibility Segregation czsto jest okrelane jako
wzorzec projektowy bd wzorzec architektoniczny. Mija si to z prawd. Dana
implementacja CQRS moe wykorzystywa zdefiniowane wzorce, jednak na
poziomie teoretycznym jest to po prostu podejcie do tworzenia
oprogramowania. Podejcie, ktre wpywa na system ju w fazie budowania
modelu reprezentujcego domen.

Kluczowa i najwaniejsza zasada CQRS mwi, e jeden model w systemie to


za mao do wydajnej pracy zarwno systemu, jak i programisty. Bardzo
czsto mona spotka aplikacje, w ktrych klasy budujce model koncepcyjny
s po prostu odwzorowaniem struktury bazy danych. Wwczas najprostsze
moliwe przedstawienie schematu komunikacji wyglda tak:

CQRS pragmatycznie | devstyle.pl


CQRS pragmatycznie

Rysunek 2. Diagram koncepcyjny standardowego systemu.

CQRS pragmatycznie | devstyle.pl


CQRS pragmatycznie
Jedyny model suy zarwno do dostarczania klientom (aplikacjom, usugom,
przegldarkom) informacji o systemie, jak te wykonywania operacji
wchodzcych w skad logiki biznesowej. Ten model to nader czsto kod
wygenerowany na podstawie bazy danych zaimportowanej do ulubionego
IDE.

O problemach wynikajcych z takiego podejcia mona poczyta w kolejnych


akapitach. Teraz skupimy si na tym, co proponuje nam CQRS:

CQRS pragmatycznie | devstyle.pl


CQRS pragmatycznie

Rysunek 3. Diagram koncepcyjny systemu CQRS.

CQRS to po prostu utworzenie wicej ni jednego modelu w systemie. Przez


model mona rozumie zestaw klas zatem otrzymujemy przynajmniej dwie
niezalene od siebie grupy klas, dedykowane do odczytania lub modyfikacji
stanu. Separacja operacji odczytu i zapisu jest sercem CQRS.

CQRS pragmatycznie | devstyle.pl


CQRS pragmatycznie
Jeden (lub wicej) model posuy do odczytu, wic bdzie zoptymalizowany
pod konkretne scenariusze wywietlania danych w aplikacji. Gro takich
scenariuszy wymaga zdenormalizowanych danych gotowych do wywietlenia
uytkownikowi bd dostarczenia zewntrznemu systemowi. Ten model czsto
nie zawiera adnej logiki innej ni przygotowanie informacji w odpowiednim
formacie: zastosowanie grupowania czy agregacji. Koncepcyjnie jego zadaniem
jest obsuga dowolnych zapyta, czyli: Queries.

Drugi model to kod logiki biznesowej. Po zastosowaniu takiego podejcia atwo


si zdziwi, jak wiele relacji pomidzy encjami po stronie write modelu po
prostu znika. Model domeny niesamowicie si upraszcza, poniewa w ani
jednym miejscu nie musi przewidywa ani obsugiwa dziesitkw scenariuszy
wymagajcych przedstawianie informacji.

Tak naprawd to jest serce CQRS. Wszystkie pozostae kwestie s pochodn


rekomendacji: jeden model w systemie to za mao.

Read side w praktyce


Do pracy na jedynym modelu suy rodzina znanych wszystkim bibliotek:
ORMy, czyli Object/Relational Mappers. CQRS skania do refleksji: czy
optymalnie wykorzystujemy ORMy? Czy zdajemy sobie spraw z ich saboci i
prbujemy je niwelowa? Przeomowym punktem w podejciu do tworzenia
nowoczesnego oprogramowania moe by uwiadomienie sobie, i te
biblioteki nie s najlepszym narzdziem do pobierania danych. Tak! wietnie
operuj one na grafach obiektw, ledz zmiany, niesamowicie upraszczaj
proces zapisywania zmian w pojedynczych agregatach i ich zalenociach,
ale zbudowanie za ich pomoc wydajnego, skomplikowanego zapytania jest
niezmiernie trudne. I to nie tylko dla pocztkujcych. Nie powinnimy traktowa
ORM jako generatora SQL. wietnym generatorem SQL, lepszym od ORMw,
jest prawie kady programista.

CQRS pragmatycznie | devstyle.pl


CQRS pragmatycznie
Standardowe podejcie do pisania aplikacji moe skutkowa bardzo
niewydajnym procesem kodowania. Obrazuje to poniszy zrzut jednego
deweloperskiego ekranu. Czy Czytelnik jest w stanie wydedukowa co si tu
dzieje?

Rysunek 4. Proces dodawania nowej funkcji do systemu przez programist.

Dostajc do oprogramowania now funkcj majc za zadanie wywietlenie


danych na ekranie czsto musimy wykona kilka powtarzalnych krokw, ktre
przedstawia powyszy obrazek. Co po kolei robimy jako programici?

Po pierwsze (rys. 4A): piszemy zapytanie SQL, ktre finalnie aplikacja powinna
wykona na bazie danych. Piszemy je w edytorze tekstu, testowo rcznie
uruchamiamy na bazie i przechodzimy do kolejnego kroku.

Drugi krok (rys. 4B) to podczenie si profilerem do naszej bazy danych, aby
monitorowa tre zapyta wysyanych przez aplikacj. Dziki temu
zweryfikujemy, czy faktycznie wykonywany jest oczekiwany SQL.

CQRS pragmatycznie | devstyle.pl


CQRS pragmatycznie
Krok trzeci (rys. 4C) to tumaczenie zapytania SQL na ORM, ktrego uywany
w danym projekcie. W przypadku .NET moe to by Entity Framework czy
NHibernate, a w Javie na przykad Hibernate. Programici Ruby
najprawdopodobniej wykorzystaj Active Record. Zao si, e wielu
Czytelnikw przechodzio przez ten proces i na dugie godziny utkno na etapie
dostosowywania ORMa tak, aby efektem byo dokadnie takie zapytanie jakie
jest wymagane przy danym scenariuszu.

Wreszcie (rys. 4D): uruchamiamy aplikacj i uywamy jej, powodujc


uruchomienie zaimplementowanej wanie logiki. W profilerze weryfikujemy
SQL i powtarzamy powrt do kodu tak dugo, a wreszcie efekt nas
satysfakcjonuje.

W tym momencie przedstawione kroki powinny zosta zakwestionowane.


Mog pojawi si przynajmniej dwie wtpliwoci: dlaczego najpierw piszemy
SQL? oraz dlaczego nie testujemy tego automatycznie?.

Pierwsz wtpliwo rozwia jest atwo: od razu definiujemy dokadny taki


tekst wysyany do bazy danych, poniewa moe on wykorzysta istniejce
indeksy. Programista o nich wie, a ORM niekoniecznie. Dodatkowo zapytanie
to nie pobiera wicej danych, ni potrzeba, unikamy tzw. bdu select *. Z
drugiej strony: pobieramy wszystkie dane, ktre s wymagane w tym
scenariuszu. Dymy, o ile to moliwe, aby cao zamkn w jednym
odwoaniu do bazy.

CQRS pragmatycznie | devstyle.pl


CQRS pragmatycznie
Wtpliwo zwizana z testami automatycznymi wymaga duszego
wyjanienia. To prawda, powinnimy unika testw manualnych, szczeglnie na
etapie tworzenia kodu. Jednak w tym przypadku: jak upewni si, e zapytanie
jest wystarczajco wydajne? W programistycznej bazie danych
najprawdopodobniej posiadamy bardzo niewiele rekordw, wic nawet
niepoprawne zapytanie wykona si szybko i nie zmierzymy tego w testach.
Moglibymy co prawda do celw testowych wygenerowa miliony wierszy, lecz
taki proces bardzo wyduy czas wykonywania testw a i tak nie zasymuluje
prawdziwego zachowania produkcyjnego rodowiska. Jak upewni si, e
pobieramy naraz wszystkie potrzebne dane, nie uciekajc si do mechanizmu
lazy loading, czyli automatycznego docigania danych przez ORM? Taka
luka prowadzi do bdu zwanego select N+1 i jest czst przyczyn
problemw wydajnociowych. Wreszcie: moemy ulec pokusie uruchamiania
testw na innym silniku bazy danych dziaajcym w pamici ni tym
faktycznie uywanym na produkcji. O ile taka praktyka jest niejednokrotnie
przydatna, to zastosowanie jej w niewaciwym przypadku przysporzy wicej
kopotw ni poytku. To zagadnienie wykracza jednak poza zakres niniejszego
artykuu. Wszystkie te kwestie DA SI zaadresowa w testach
automatycznych, lecz jest prostsza droga.

Jak CQRS moe nam pomc?


Przyjrzyjmy si temu na przykadzie. Zamy, e tworzony system suy do
wywietlania reklam klientom firm. Czyli piszemy jeden z 90% systemw w
aktualnej rzeczywistoci :).

Klasa Advertisement reprezentuje reklam pokazywan uytkownikowi.


Nieaktualne reklamy archiwizujemy zamiast usuwa je z bazy. Dodatkowo
projektant zama w tym przypadku dobr praktyk i postanowi, e grafika
reklamy znajdzie si w bazie danych zamiast na dysku:

CQRS pragmatycznie | devstyle.pl


CQRS pragmatycznie

1 public class Advertisement

2 {

3 public int Id { get ; set ; }

4 public string Title { get ; set ; }

5 public string Content { get ; set ; }

6 public bool IsArchived { get ; set ; }

7 public byte [] Picture { get ; set ; }

8 }

Listing 1. Klasa reprezentujca reklam pokazywan uytkownikowi.

Bdziemy zlicza liczb wywietle reklam, aby obserwowa trendy


popularnoci reklam:

1 public class AdView

2 {

3 public Advertisement ParentAd { get ; set ; }

4 public DateTime ViewDate { get ; set ; }

5 }

Listing 2. Klasa reprezentujca pojedyncze wywietlenie reklamy.

CQRS pragmatycznie | devstyle.pl


CQRS pragmatycznie
Uytkownik systemu to najprostsza moliwa klasa User:

1 public class User

2 {

3 public int Id { get ; set ; }

4 public string UserName { get ; set ; }

5 }

Listing 3. Klasa reprezentujca uytkownika systemu.

I wreszcie: firma bdca klientem aplikacji. Posiada zdefiniowan list swoich


klientw (naszych uytkownikw) oraz list reklam, ktre chciaaby tym
klientom wywietli:

1 public class Company

2 {

3 public int Id { get ; set ; }

4 public string Name { get ; set ; }

5 public List<User> Customers { get ; set ; }

6 public List<Advertisement> Ads { get ; set ; }

7 }

Listing 4. Klasa reprezentujca uytkownika firm klienta systemu.

CQRS pragmatycznie | devstyle.pl


CQRS pragmatycznie
Scenariusz, jaki mamy do napisania, wymaga pobrania listy reklam do
wywietlenia uytkownikowi aktualnie przegldajcemu stron. Musimy zatem
pobra reklamy wszystkich firm, ktrych ta osoba jest klientem. Pokazywanie
reklam zarchiwizowanych nie ma sensu, zatem te pomijamy. Chcielibymy
zobrazowa rwnie popularno kadej reklamy, zatem przyda si liczba jej
dotychczasowych obejrze. I dodatkowe zaoenie: tworzony widok listy reklam
nie zawiera grafik te s uywane tylko na ekranie szczegw reklamy.

Brzmi sensownie? Jestem przekonany, e kady z Czytelnikw spotka si z


podobnym scenariuszem.

Teraz wyobramy sobie, e mamy tylko jeden model przedstawiajcy nasz


domen: klasy przedstawione powyej. Schody zaczn si ju w momencie
wykonywana podstawowej operacji: pobrania danych za pomoc ORM.
Bdziemy mieli tutaj JOINy pomidzy tabelami. Pojawi si podzapytanie
zliczajce liczb wywietle. Musimy odfiltrowa reklamy zarchiwizowane. I
oczywicie upewni si, e potencjalnie cika kolumna Picture nie znajdzie
si na licie wybieranych kolumn.

Czy nie prociej bdzie zdefiniowa osobn klas skadow Read Modelu
obsugujc ten jeden konkretny scenariusz? Zawierajc wycznie dane
wymagane przez implementowany wanie ekran? Oczywicie, e tak!

Zacznijmy od napisania odpowiedniego zapytania SQL:

CQRS pragmatycznie | devstyle.pl


CQRS pragmatycznie

1 select a.Id, a.Title, a.Content

2 , ( select count (*) from AdViews v where v.ParentAdId = a.Id)

3 from Advertisements a

4 join Companies c on a.CompanyId = c.Id

5 join Users u on u.CompanyId = c.Id

6 where

7 a.IsArchived = 0

8 and u.Id = <userId>

Listing 5. Zapytanie dostarczajce reklamy do wywietlenia uytkownikowi.

Wszystkie przedstawione warunki mamy spenione, a spdzilimy nad tym


krokiem na pewno zdecydowanie mniej czasu ni gdybymy zabrali si do tego
z pomoc dowolnego ORMa.

Skoro mamy gotowe zapytanie to jak uy go w aplikacji? Rozwizanie tego


dylematu jest banalnie proste: stwrzmy widok!

CQRS pragmatycznie | devstyle.pl


CQRS pragmatycznie

1 CREATE VIEW advertisements_for_ads_page AS

2 select u.Id as UserId, a.Id, a.Title, a.Content

3 , ( select count (*) from AdViews v where v.ParentAdId = a.Id)

4 from Advertisements a

5 join Companies c on a.CompanyId = c.Id

6 join Users u on u.CompanyId = c.Id

7 where

8 a.IsArchived = 0

Listing 6. Zapytanie zapisane w postaci widoku.

Do klauzuli select musimy doda UserId, aby by w stanie filtrowa w widok


na potrzeby jednego uytkownika.

Majc spaszczony widok dedykowany dla konkretnego scenariusza jestemy


w stanie o wiele prociej przetestowa go za pomoc testw automatycznych,
ni gdybymy to robili na grafie obiektw, penym zbdnych atrybutw i relacji.
Dodatkowo niektre silniki baz danych pozwol take na materializacj
widoku i naoenie na indeksw w razie potrzeby.

Przyzwyczajeni do ORMw moemy utworzy klas mapujc ten widok i


wygenerowa najprostsze zapytanie, ktre w kadej takiej bibliotece bdzie
wyglda identycznie:

CQRS pragmatycznie | devstyle.pl


CQRS pragmatycznie

1 select * from advertisements_for_ads_page where userid = X

Listing 7. Pobranie danych z uyciem widoku.

Programici .NET maj do dyspozycji bardzo przyjemn bibliotek open source


o nazwie Simple.Data, w ktrej zapytanie bdzie wygldao tak:

1 IEnumerable<dynamic> ads = _simpleData.advertisements_for_ads_page.FindAllByUserId(userId);

Listing 8. Przykad wykorzystania biblioteki Simple.Data.

Simple.Data odnajdzie widok w bazie i skonstruuje odpowiedni SQL na


podstawie nazwy metody FindAllBy Jej wykorzystanie wyeliminuje rwnie
konieczno implementacji klasy poredniej reprezentujcej widok, poniewa
zwracane przez ni obiekty typu dynamic moemy bezporednio przekaza do
silnika renderujcego wynik. Jednak, chcc zachowa stosunkow
uniwersalno niniejszego artykuu, nie bdziemy zagbia si bardziej w tajniki
i moliwoci tej biblioteki.

Podsumowujc: dziki takiej strategii proste i cakowicie standardowe


wymagania s bardzo proste w implementacji. Wystarczyo uzmysowi sobie,
e istniejce ju w systemie klasy nie nadaj si do ich realizacji. Traktujc
odczyt i wywietlanie danych jako odrbny subsystem uwalniamy si od
wszelkich ogranicze, na jakie moemy natkn si w standardowym, jedno-
modelowym podejciu.

CQRS pragmatycznie | devstyle.pl


CQRS pragmatycznie

Write side w praktyce


Po odseparowaniu pobierania danych od prawdziwej logiki biznesowej
zostajemy z kolejnym problemem: jak j zorganizowa? Istnieje wiele praktyk
mwicych w jaki sposb mona do tego podej. Oczywistym przykadem
moe by DDD Domain DrivenDesign. Praktyka ta cile definiuje z jakiego
typu elementw naley budowa system i gdzie umieszcza jakie konstrukcje.
Jednak chyba wszyscy spotkalimy si z systemami zawierajcymi logik
biznesow w kadej warstwie. Poczynajc od kontrolerw, przechodzc przez
obiekty/serwisy domenowe, na procedurach skadowanych koczc. Nie
zawsze wynika to z niedbalstwa bd niewiedzy programistw. Taka jest
niestety specyfika naszej brany. Kto pierwszy rzuci kamieniem?

System z rozproszon logicznie odpowiedzialnoci jest bardzo trudny w


utrzymaniu. Zrozumienie jego dziaania wymaga analizy praktycznie caego
kodu, a odszukanie przyczyny buga bd miejsca odpowiedniego do
zaimplementowania nowej funkcji wiele pracy. Takie rozwizania
charakteryzuj si rwnie bardzo du liczb zalenoci pomidzy
komponentami, przez co zarwno ich debuggowanie jak i automatyczne
testowanie nie jest zadaniem prostym ani przyjemnym. Zreszt wszyscy
znamy to z praktyki, prawda?

Wrmy do przykadowego systemu wywietlajcego reklamy i dodajmy


kolejne wymaganie. Tym razem chcemy umoliwi uytkownikom oznaczenie
dowolnej z nich jako ulubion, aby w przyszoci by w stanie lepiej profilowa
ich upodobania bd da opcj przegldania reklam oznaczonych w taki
sposb. Jest to do powana zmiana, ktra wpynie na ksztat klasy Users:

CQRS pragmatycznie | devstyle.pl


CQRS pragmatycznie

1 public class User

2 {

3 /// other properties...

4 public List<Advertisement> InterestingAds { get ; set ; }

5 }

Listing 9. Dodanie interesujcych reklam do klasy uytkownika.

Implementacja takiego scenariusza wydaje si do prosta: wystarczy do tego


identyfikator uytkownika oraz oznaczonej reklamy. Kod realizujcy to w wielu
ORMach wygldaby podobnie:

1 var user = session.Get<User>(userId);

2 var ad = session.Get<Advertisement>(adId);

3 user.InterestingAds.Add(ad);

4 session.Save(user);

Listing 10. Kod oznaczajcy reklam jako interesujc.

CQRS pragmatycznie | devstyle.pl


CQRS pragmatycznie
Jednak: gdzie go umieci? Przy gonicych terminach mona ulec pokusie
wstawienia go po prostu do akcji kontrolera. Rozwizanie o tyle szybkie, co
niepraktyczne: kontrolery nie powinny zawiera takiej logiki, prawda? Wic
moe do AdsService, zawierajcego wszystkie operacje na reklamach? To
moe wydawa si naturalnym i prawidowym podejciem, lecz wspomniana
klasa usugi operujcej na reklamach bardzo szybko rozronie si do wielu
tysicy linii kodu.

Dziki Command Query Responibility Segregation bylimy w stanie uproci


stron odczytujc dane: Queries. Spjrzmy, jak to podejcie uproci kod
odpowiedzialny za wykonywanie logiki biznesowej. Poznajmy Commands, czyli
komendy.

Zaoenie jest proste: kada operacja wykonujca modyfikacje danych w


systemie powinna by zamodelowana za pomoc komendy. Pamitacie, e do
zrealizowania omawianego przypadku wystarcz tylko dwa identyfikatory?
Wic:

1 public class MarkAdAsInterestingCommand

2 {

3 public int UserId { get ; set ; }

4 public int AdId { get ; set ; }

5 }

Listing 11. Komenda reprezentujca zamiar oznaczenia reklamy.

CQRS pragmatycznie | devstyle.pl


CQRS pragmatycznie
Jednak przyda si troch infrastrukturalnej otoczki, aby usprawni nawigacj
po kodzie oraz umoliwienie cywilizowanego i czytelnego sposobu.

Niech kada komenda implementuje pusty interfejs, tzw. marker interface.


Pozwoli to zamodelowa pozostae struktury uywajc zalet programowania
obiektowego:

1 public interface ICommand

2 {

3 }

Listing 12. Marker interface dla wszystkich komend.

Komenda jedynie reprezentuje intencj wykonania czynnoci, natomiast samo


jej wykonanie niech znajduje si w dedykowanej do tego klasie implementujcej
interfejs:

1 public interface IHandleCommand

2 {

3 }

Listing 13. Marker interface dla wszystkich command handlerw.

CQRS pragmatycznie | devstyle.pl


CQRS pragmatycznie
Pusty interfejs w tym przypadku na niewiele si zda i ponownie: posuy
bardziej do spicia caej infrastruktury w cao. Jzyki z typami generycznymi
pozwol na rozszerzenie go o:

1 public interface IHandleCommand<TCommand> : IHandleCommand

2 where TCommand : ICommand

3 {

4 void Handle(TCommand command);

5 }

Listing 14. Docelowy interfejs dla wszystkich command handlerw.

Interfejs IHandleCommand<T> definiuje faktyczn operacj zajmujc si


przetworzeniem komendy. Wykorzystanie typw generycznych uproci kod i
zmaksymalizuje wykorzystanie potencjau drzemicego w kompilatorze.

Majc reprezentacj komendy oraz klasy odpowiedzialnej za jej przetworzenie


moemy skupi si na sposobie uruchamiania przetwarzania komendy. Na
poziomie interfejsu konstrukcja taka jest bardzo prosta:

1 public interface ICommandBus

2 {

3 void SendCommand<T>(T cmd) where T : ICommand;

4 }

Listing 15. Interfejs komponentu wysyajcego komendy do systemu.

CQRS pragmatycznie | devstyle.pl


CQRS pragmatycznie

Konkretna implementacja takiej szyny komend moe mie wiele postaci. Jedna
z nich to:

1 public class CommandBus : ICommandBus

2 {

3 private readonly Func<Type, IHandleCommand> _handlersFactory;

4 public CommandBus(Func<Type, IHandleCommand> handlersFactory)

5 {

6 _handlersFactory = handlersFactory;

7 }

8 public void SendCommand<T>(T cmd) where T : ICommand

{
9

var handler = (IHandleCommand<T>)_handlersFactory( typeof (T));


10
handler.Handle(cmd);
11
}
12
}
13

14

15

Listing 16. Przykadowa implementacja szyny komend.

CQRS pragmatycznie | devstyle.pl


CQRS pragmatycznie

Umiejtne uycie kontenera Dependency Injection pozwoli na bardzo klarowne


poczenie komend z odpowiednimi dla nich handlerami w momencie
uruchomienia aplikacji. Naley zwrci uwag na fakt, e powyszy kod
wymaga posiadania jednej klasy przetwarzajcej kad komend. Zasada:
jedna komenda jeden handler.

Po przeanalizowaniu zaoe stojcych za interfejsem ICommandBus mona


dostrzec ciekaw charakterystyk tego komponentu: kada operacja w
systemie przechodzi przez metod SendCommand. Nikt nie moe zrobi
niczego z pominiciem tych kilku linii kodu. Mona wykorzysta to zjawisko
dodajc w tym miejscu globalny audyt logowanie co, kto, kiedy. Albo
obsug wyjtkw. Interesujcym pomysem jest rwnie wykrywanie wolnych
operacji w systemie: kada komenda, ktrej kompletne wykonanie trwa duej
ni 1 sekunda (czy inny, dowolnie zdefiniowany, zakres czasu) powoduje
powiadomienie administratora lub zespou projektowego o potencjalnym
problemie wydajnociowym. Pomysy mona mnoy. Warto zauway fakt, i
cay prezentowany kod jest niezaleny od frameworka czy technologii
wykorzystywanej do zbudowania systemu.

Po rozbiciu funkcji penionych przez system na granularne komendy okazuje si,


i wikszo klas odpowiedzialnych za ich przetwarzanie jest prosta. A co za
tym idzie: atwo jest je przeczyta, zrozumie i utrzymywa. I, co bardzo
wane: przetestowa!

CQRS pragmatycznie | devstyle.pl


CQRS pragmatycznie
Brakujcym elementem kompletnego rozwizania jest handler nowej komendy.
Co ciekawe, nawet w tak prostym przypadku mamy przynajmniej dwie moliwe
drogi. Moemy pokusi si o rozszerzenie i wykorzystanie modelu, jak zostao
to przedstawione na kodzie wprowadzajcym w zagadnienie. Moemy jednak
zastanowi si: jaka jest ostateczna operacja realizujca zadane wymaganie?
Czy naprawd musimy operowa na grafie obiektw ze zdefiniowanymi
relacjami? A moe wystarczy:

CQRS pragmatycznie | devstyle.pl


CQRS pragmatycznie

1 public class MarkAdAsInterestingHandler : IHandleCommand<MarkAdAsInterestingCommand>

2 {

3 private readonly dynamic _simpleData;

4 public MarkAdAsInterestingHandler(dynamic simpleData)

5 {

6 _simpleData = simpleData;

7 }

8 public void Handle(MarkAdAsInterestingCommand command)

{
9
_simpleData.InterestingAds.Insert(
10
UserId: command.UserId, AdvertisementId: command.AdId
11
);
12
}
13
}
14

15

16

Listing 17. Klasa realizujca oznaczenie reklamy jako interesujcej.

CQRS pragmatycznie | devstyle.pl


CQRS pragmatycznie
Napisanie tego kodu (z pokrywajcymi go testami!) nie powinno zaj wicej
ni kwadrans.

Tkwi tutaj wielki potencja. Nie mamy ju problemu z umiejscowieniem logiki


biznesowej. Kada operacja to komenda, a kada komenda ma swj handler.
Logika znajduje si tylko tam: w handlerach.

Dyscyplina w tym zakresie niesie za sob kolejn korzy: wystarczy odnale


wszystkie implementacje interfejsu ICommand (co kade IDE potrafi zrobi bez
problemu), aby dowiedzie si dokadnie: co robi system?.

Przytaczajc po raz kolejny aspekt testowalnoci systemu: przy takim


podejciu otrzymujemy struktury idealnie wprost nadajce si do testowania.
Testy typowo jednostkowe przeprowadzi jest atwo, gdy kady handler to
niewielka klasa, realizujca tylko jedno konkretne zadanie, niewymagajce
wielu zalenoci. Natomiast pene testy integracyjne wymagaj jedynie
skonfigurowania kontenera Dependency Injection i wysyanie komend przez
ICommandBus.

Podsumowanie
CQRS mona zaimplementowa na wiele sposobw. Przedstawione tutaj
podejcie pozwala na sprbowanie swoich si w praktycznie dowolnym
systemie. Nie wymaga ono zmiany bazy danych czy wprowadzania szyny
wiadomoci do projektu. Wystarczy znajomo refactoringu extract method to
class.

Warstwa kontrolerw wyglda nareszcie tak jak powinna wyglda: suy


wycznie do dystrybucji polece w gb aplikacji. Kady kontroler (czy inny
punkt wejcia, zalenie od zastosowanej architektury) potrzebuje tylko jednej
z dwch zalenoci: albo dostpu do danych (Simple.Data, inny ORM,
repozytorium) albo implementacji ICommandBus.

CQRS pragmatycznie | devstyle.pl


CQRS pragmatycznie
T koncepcj mona rozszerza o kolejne pojcia jak chociaby zdarzenia
jednak to wydaje si by tematem na osobny artyku.

Chcesz wicej?

Zapraszam na devstyle.pl!

W szczeglnoci moe zainteresowa Ci kategoria CQRS.


Tam znajdziesz teksty rozwijajce temat
Command Query Responsibility Segregation.

Do przeczytania!

CQRS pragmatycznie | devstyle.pl

You might also like