Cloud Crypto Tool – CQRS oraz architektura aplikacji

Jak już zapowiedziałem w jednym poście zdecydowałem się na wykorzystanie wzorca CQRS (Command Query Responsibility Segregation) w projekcie mojej aplikacji – Cloud Crypto Tool. CQRS wywodzi się on z zasady CQS (Command Query Separation) głoszącej, że każda metoda powinna albo modyfikować stan obiektu, albo zwracać pewną wartość, ale nigdy nie powinna robić tych dwóch rzeczy jednocześnie. Jeżeli metoda wykonuje jakąś logikę biznesową, przez co stan obiektu ulega dowolnej modyfikacji, to nie powinna ona zwracać żadnych danych. Podobna sytuacja ma się z odczytem, odpytywanie bazy nie powinno niczego modyfikować w aplikacji. Stąd mamy podział na komendy (Commands) oraz zapytania (Queries) odpowiedzialne za poszczególne akcje systemu.

Komendy – zmieniamy stan obiektu, ale nic nie zwracamy

Na początku zaczynamy od zdefiniowania abstrakcji dla każdej z komend. Zwykły, pusty interface będzie dla nas wystarczający, będzie służył jako kontrakt dla każdego z poleceń. Warto zaznaczyć, że komenda to tylko sam zamiar tego co będziemy chcemy zrobić, za jej wykonanie odpowiada tzw. Command Handler, dla którego również należy przygotować osobną warstwę abstrakcji:

W naszej aplikacji zaczniemy od możliwości dodawania nowych tekstów jawnych (plaintexts), które będą wysyłane POST’em do aplikacji. Następnie dzięki dodatkowej warstwie domenowej zapisywane są one w bazie danych. Tekst jawny stanowi najzwyklejszą wiadomość, sformułowaną z wykorzystaniem języka naturalnego. W przypadku opracowanej aplikacji będzie to język angielski, aczkolwiek każdy inny tekst również będzie akceptowany. Teksty jawne szyfrowane są przy pomocy dowolnych algorytmów szyfrujących, w skutek czego powstaje nieczytelny dla nikogo szyfrogram. Komenda AddPlaintextCommand wygląda następująco:

Jak już wspomniałem powyżej komenda to tylko zamysł. Dla każdego polecenia musi istnieć odpowiedni handler realizujący to polecenie:

Zwróć uwagę, że metoda Handle nie zwraca żadnego wyniku – tak jak było w założeniach. Jak pewnie zauważyłeś w tym miejscu wykorzystywane jest obiekt repozytorium (wstrzykiwany za pomocą Autofac’a). Repozytoria odpowiedzialne są za komunikację z bazą, na której będziemy wykonywać operację zapisu:

Pozostał jeszcze jeden element, aby cały ten proces mógł działać prawidłowo, mianowicie CommandBus. Jest to komponent odpowiadający za dopasowanie odpowiednich handler’ów do komend – co można w łatwy i przyjemny sposób zrealizować z wykorzystaniem wstrzykiwania zależności (Dependency Injection). Następnie wywoływana jest sama komenda – metoda Handle(). Przykładowa implementacja z wykorzystaniem Autofac’a:

Zapytania – pobieramy dane z bazy, ale niczego nie wolno nam modyfikować

Podobnie jak w przypadku komend, zaczynamy od stworzenia dodatkowej warstwy abstrakcji dla wszystkich zapytań:

Dodajmy teraz przykładowe zapytanie GetAllPlaintexts odpowiedzialne za wyciągnięcia wszystkich tekstów jawnych jakie zostały wprowadzone w aplikacji. W tym przypadku nie chcemy zwracać obiektów domenowych, tylko tzw. obiekty DTO (Data Transfer Object). Obiekty tego typu wykorzystywane są do przenoszenia informacji pomiędzy różnymi warstwami systemu, najczęściej składają się tylko z setterów oraz getterów. Do mapowania obiektów z domeny na DTO wykorzystałem bibliotekę AutoMapper. Do tego dodałem własne metody rozszerzeń (Extension Methods) pozwalające na rzutowanie obiektów na DTO z pomocą LINQ – .MapTo<>()

Potrzebny nam będzie jeszcze odpowiedni dyspozytor (Query Dispatcher) wywołujący zaimplementowane zapytania:

Zapytania mogą być dodatkowo wzbogacane o różnego rodzaju filtry czy agregaty, ale o tym innym razem 🙂

Jak wykorzystać taki CQRS?

Zostało mi jeszcze przedstawić w jaki sposób należy z tego korzystać. Poniżej zamieszam przykładowy PlaintextController służący do obsługi żądań REST’owych:

Dzięki tego typu architekturze uzyskujemy klarowne API, z czytelnym podziałem na dwie części. Jedną odpowiedzialną za pobieranie informacji, oraz drugą wprowadzającą zmiany w systemie. Do opisanego podejścia często wprowadza się dodatkowy wzorzec jakim jest Event Sourcing. Odpowiedzialny jest on za przechowywanie listy zmian w aplikacji, ale zostawiam go sobie na kiedy indziej 🙂 Po więcej szczegółów zapraszam do publicznego repozytorium znajdującego się na GitHub’ie.