Przejdź do treści stopki
Iron Academy Logo
Najczęstsze problemy w C#

CQRS Handler (C#) z logiką domeny i pipeline'ami – Wyjaśnione za pomocą wideo Dereka Comartina

Derek Comartin
8m 03s

Command Query Responsibility Segregation (CQRS) to potężny wzorzec projektowy w aplikacjach .NET, który pomaga utrzymać wyraźne rozdzielenie między operacjami odczytu i zapisu. To rozdzielenie umożliwia lepszą skalowalność, testowalność i kontrolę nad złożoną logiką biznesową. Jednak w miarę rozrastania się aplikacji, Twoje obsługujące CQRS w C# mogą stać się przeładowane logiką, co utrudnia ich utrzymanie i testowanie.

W swoim wnikliwym filmie "Clean Up Bloated CQRS Handlers with Domain Logic & Pipelines" Derek Comartin omawia, jak oczyścić takie gestory poleceń CQRS przez przeniesienie logiki do modeli domeny i strukturę pipeline zarządzającą logiką krok po kroku. W tym artykułe zbadamy podejście Dereka szczegółowo i nauczymy się budować bardziej łatwe w utrzymaniu implementacje CQRS w C#.

Problem z przeładowanymi gestory

Derek zaczyna od podkreślenia powszechnego scenariusza w wielu aplikacjach webowych: otwierasz gestora CQRS w C#, i jest bałagan. Istnieje walidacja danych, autoryzacja, logika biznesowa, przejścia stanu, publikowanie zdarzeń, logowanie - wszystko splątane razem.

Ilustruje to używając obiektu polecenia do wysyłki przesyłki. Gestor odpowiada za:

  • Uzyskanie dostępu do magazynu danych w celu załadowania przesyłki.

  • Sprawdzenie, czy status przesyłki jest gotowy.

  • Aktualizację statusu (tj. aktualizację danych).

  • Zapisanie zmian z powrotem do warstwy dostępu do danych.

  • Wysłanie e-maila.

  • Publikowanie zdarzenia domeny.

Wszystko to dzieje się w jednym miejscu. To narusza intencję wzorca CQRS, którego celem jest rozdzielenie trosk i poprawa wydajności oraz łatwości utrzymania.

Przenoszenie logiki domeny do modelu danych

Pierwszym krokiem Dereka jest przeniesienie walidacji i logiki przejścia stanu do modelu danych. Tworzy metodę Dispatch() w klasie Shipment. Teraz tutaj znajduje się logika domeny.

Zamiast ręcznie sprawdzać status wysyłki w handlerze, logika jest zamknięta w tej metodzie, co zapewnia integralność danych i spójne działanie niezależnie od tego, gdzie uruchamiane jest wysyłanie. Ma to kluczowe znaczenie dla wdrożenia czystej architektury w aplikacji opartej na CQRS.

Na przykład w każdym miejscu, w którym wywołuje się metodę shipment.Dispatch(), automatycznie wykonywane są wszystkie walidacje i przejścia między stanami. Jest to zgodne z wzorcem projektowym CQRS, pomagającym zachować wyraźny podział między obsługą a logiką domeny.

Wartość scentralizowania logiki

Derek zwraca uwagę, że tego rodzaju zmiana nie polega na dodawaniu zbędnej abstrakcji. Chodzi raczej o scentralizowanie logiki wykorzystywanej w różnych częściach kodu aplikacji. Jeśli wiele modułów obsługi poleceń musi wysłać przesyłkę, ta niestandardowa logika powinna znajdować się w jednym miejscu — wewnątrz modelu domeny.

Dzięki temu model danych staje się bardziej niezawodny, a implementacje obsługi CQRS w języku C# są prostsze i łatwiejsze w utrzymaniu.

Przedstawiamy wzorzec Pipeline

Aby jeszcze bardziej uporządkować obsługę poleceń, Derek wprowadza wzorzec potoku. Ta struktura przetwarza polecenie jako sekwencję małych, jednofunkcyjnych kroków, z których każdy pobiera obiekt kontekstowy i wywołuje następny krok.

Koncepcyjnie przypomina to oprogramowanie pośredniczące ASP.NET Core, a każdy krok skupia się na konkretnej części procesu:

  • Pobieranie przesyłki (tj. odczyt danych)

  • Wysyłanie (wykonywanie operacji zapisu)

  • Wydarzenia związane z publikacją

  • Zapisywanie w magazynie danych

Te kroki wykorzystują wspólny obiekt polecenia, który przepływa przez potok. Tworzy to przejrzystą i modułową implementację rozdzielenia odpowiedziąlności za polecenia i zapytania.

Przykładowa implementacja potoku

W swoim przykładowym wdrożeniu Derek organizuje proces w następujące etapy:

  • Ładowanie przesyłki – pobieranie danych z warstwy dostępu do danych przy użyciu repozytorium.

  • Wysyłanie przesyłki – wywołanie metody Dispatch() w celu zastosowania logiki domeny.

  • Dodawanie zdarzenia domeny – dołączanie zdarzenia "ShipmentDispatched" do kontekstu.

  • Wydawanie zdarzeń – wysyłanie zdarzeń w celu powiadomienia systemów zewnętrznych.

  • Zapisywanie zmian – trwałe zapisywanie aktualizacji w magazynie danych.

Każdy krok reprezentuje odrębny element logiki poleceń, co poprawia walidację danych i pozwala na rozdzielenie odpowiedziąlności.

Derek zauważa również, że powiadomienia e-mailowe są teraz obsługiwane oddzielnie poprzez reakcję na zdarzenie domenowe. Jest to zgodne z zasadami event sourcing i sprzyja spójności ostatecznej.

Korzyści związane z testowaniem i łatwością utrzymania

Jedną z największych zalet tego wzorca jest możliwość testowania. W przypadku dużego modułu obsługi poleceń może występować kilka zależności (np. repozytoria, usługi pocztowe, loggery). Jednak gdy podzielisz handler na etapy potoku, każdy z nich wymaga tylko kilku zależności.

To modułowe podejście pozwala łatwo testować poszczególne etapy za pomocą wstrzykiwania zależności, wykorzystując w razie potrzeby fałszywe obiekty lub makiety. Na przykład, jeśli testujesz krok wywołujący Dispatch(), nie musisz symulować usługi poczty elektronicznej ani wydawcy zdarzeń.

Ten podział zadań jest zgodny z wzorcem CQRS (segregacja odpowiedziąlności), dzięki czemu modele odczytu i zapisu stają się bardziej przejrzyste i skoncentrowane.

Kompozycyjność i możliwość ponownego wykorzystania

Kolejną zaletą podejścia opartego na potoku jest jego modułowość. Jeśli użyjesz czegoś takiego jak wzorzec Outbox, możesz mieć pewność, że zdarzenia zostaną opublikowane dopiero po zapisaniu modeli zapisu. Ten poziom kontroli jest niezbędny w implementacjach CQRS, gdzie liczy się spójność i gwarancje dostawy.

Można również współdzielić kroki między różnymi handlerami CQRS — na przykład ogólny krok "SaveChanges" lub krok "ValidateRequest".

Korzystając z narzędzi takich jak biblioteka MediatR, która obsługuje polecenia i zapytania, można nawet zarejestrować te kroki poprzez wstrzykiwanie zależności w aplikacji .NET Core przy użyciu usług IServiceCollection.

Aby skonfigurować ten system, można uruchomić polecenie Install-Package MediatR za pośrednictwem konsoli menedżera pakietów programu Visual Studio — jest to typowy krok podczas wdrażania CQRS w języku C#.

Kompromisy

Derek nie boi się większej złożoności, która wiąże się z tym podejściem. Pipeline wprowadzają pośrednictwo, a patrząc na stos wywołań, można odnieść wrażenie, że porusza się po labiryncie.

Jednak w przypadku złożonej logiki biznesowej kompromis ten często się opłaca. Jeśli handler ma ponad 10 zależności i setki linii kodu, CQRS pozwala programistom lepiej zorganizować i utrzymywać te przepływy.

Końcowe przemyślenia na temat tego, kiedy przeprowadzać refaktoryzację

Na zakończenie Derek przypomina czytelnikom, aby dokładnie zastanowili się, czy ich implementacja obsługi CQRS w języku C# jest rzeczywiście zbyt rozbudowana. Nie każdy scenariusz wymaga stosowania potoku. Jego celem jest zilustrowanie możliwości, a to programiści muszą ocenić własne wdrożenie CQRS i ustalić, czy takie wzorce będą pomocne.

Zachęca programistów do przyjrzenia się obszarom w ich kodzie, w których rozdzielenie zagadnień pomogłoby zachować spójność, uczynić kod bardziej modułowym oraz lepiej zarządzać operacjami odczytu i zapisu — zwłaszcza w aplikacjach internetowych opartych na CQRS.

Wnioski

Film Dereka Comartina stanowi praktyczny przewodnik po porządkowaniu handlerów CQRS przy użyciu enkapsulacji logiki domenowej i potoków. Takie podejście pomaga rozwiązać problemy związane z nadmiernym rozrostem kodu, promuje integralność danych i zwiększa łatwość konserwacji poprzez podział kodu aplikacji na odrębne modele.

Niezależnie od tego, czy zajmujesz się danymi pracowników, szczegółami produktów czy nowymi poleceniami użytkowników, zastosowanie wzorca CQRS z wykorzystaniem potoków i projektowania opartego na domenach sprawi, że Twój kod będzie bardziej skalowalny, łatwiejszy do testowania i bardziej niezawodny.

Dzięki wykorzystaniu obiektów transferu danych, oddzielnych modeli oraz zachowaniu wyraźnego rozdzielenia logiki odczytu i zapisu, aplikacja .NET będzie miała lepszą strukturę i będzie łatwiejsza do rozbudowy w miarę upływu czasu.

Hero Worlddot related to CQRS Handler (C#) z logiką domeny i pipeline'ami – Wyjaśnione za pomocą wideo Dereka Comartina
Hero Affiliate related to CQRS Handler (C#) z logiką domeny i pipeline'ami – Wyjaśnione za pomocą wideo Dereka Comartina

Zarabiaj więcej, dzieląc się tym, co kochasz

Tworzysz treści dla deweloperów pracujących z .NET, C#, Java, Python, czy Node.js? Zamień swoją wiedzę specjalistyczną na dodatkowy dochód!

Zespol wsparcia Iron

Jestesmy online 24 godziny, 5 dni w tygodniu.
Czat
Email
Zadzwon do mnie