Ostatnio badałem CQRS / MediatR. Ale im więcej drążę, tym mniej mi się podoba. Być może źle zrozumiałem coś / wszystko.
Zaczyna się więc niesamowicie, twierdząc, że zredukowałeś do tego kontroler
public async Task<ActionResult> Edit(Edit.Query query)
{
var model = await _mediator.SendAsync(query);
return View(model);
}
Co idealnie pasuje do cienkich wytycznych kontrolera. Pomija jednak kilka bardzo ważnych szczegółów - obsługę błędów.
Przyjrzyjmy się domyślnej Login
akcji z nowego projektu MVC
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
_logger.LogInformation(1, "User logged in.");
return RedirectToLocal(returnUrl);
}
if (result.RequiresTwoFactor)
{
return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
}
if (result.IsLockedOut)
{
_logger.LogWarning(2, "User account locked out.");
return View("Lockout");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
Konwersja, która stwarza nam wiele problemów w świecie rzeczywistym. Pamiętaj, że celem jest ograniczenie tego do
public async Task<IActionResult> Login(Login.Command command, string returnUrl = null)
{
var model = await _mediator.SendAsync(command);
return View(model);
}
Jednym z możliwych rozwiązań jest zwrócenie CommandResult<T>
zamiast a, model
a następnie obsłużenie CommandResult
filtru po działaniu. Jak omówiono tutaj .
Jedna implementacja CommandResult
może być taka
public interface ICommandResult
{
bool IsSuccess { get; }
bool IsFailure { get; }
object Result { get; set; }
}
Jednak to tak naprawdę nie rozwiązuje naszego problemu w Login
działaniu, ponieważ istnieje wiele stanów awarii. Możemy dodać te dodatkowe stany awarii, ICommandResult
ale jest to świetny początek dla bardzo rozdętej klasy / interfejsu. Można powiedzieć, że nie jest on zgodny z Single Responsibility (SRP).
Innym problemem jest returnUrl
. Mamy ten return RedirectToLocal(returnUrl);
fragment kodu. Jakoś musimy obsługiwać argumenty warunkowe w oparciu o stan powodzenia polecenia. Chociaż myślę, że można to zrobić (nie jestem pewien, czy ModelBinder może zmapować argumenty FromBody i FromQuery ( returnUrl
jest FromQuery) na pojedynczy model). Można się tylko zastanawiać, jakie szalone scenariusze mogą przyjść na później.
Sprawdzanie poprawności modelu stało się również bardziej złożone wraz ze zwracaniem komunikatów o błędach. Weź to jako przykład
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
Do modelu dołączamy komunikat o błędzie. Tego rodzaju rzeczy nie można zrobić przy użyciu Exception
strategii (jak sugerowano tutaj ), ponieważ potrzebujemy modelu. Być może możesz pobrać model z, Request
ale byłby to bardzo zaangażowany proces.
Podsumowując, trudno mi było przekonwertować tę „prostą” akcję.
Szukam danych wejściowych. Czy tutaj całkowicie się mylę?
źródło
Odpowiedzi:
Myślę, że oczekujesz zbyt wiele używanego wzoru. CQRS został specjalnie zaprojektowany, aby zaradzić różnicy w modelu między zapytaniami a poleceniami w bazie danych , a MediatR to tylko biblioteka komunikatów w trakcie przetwarzania. CQRS nie twierdzi, że eliminuje potrzebę logiki biznesowej tak, jak tego oczekujesz. CQRS to wzorzec dostępu do danych, ale twoje problemy dotyczą warstwy prezentacji - przekierowań, widoków, kontrolerów.
Myślę, że niewłaściwie stosujesz wzorzec CQRS do uwierzytelniania. Przy logowaniu nie może być modelowany jako polecenie w CQRS, ponieważ
Moim zdaniem uwierzytelnianie jest kiepską domeną dla CQRS. Przy uwierzytelnianiu potrzebujesz bardzo spójnego, synchronicznego przepływu żądania-odpowiedzi, abyś mógł 1. sprawdzić dane uwierzytelniające użytkownika 2. utworzyć sesję dla użytkownika 3. obsłużyć dowolną z wielu zidentyfikowanych przez Ciebie przypadków brzegowych 4. natychmiast przyznać lub odrzucić użytkownika w odpowiedzi.
CQRS to wzorzec, który ma bardzo specyficzne zastosowania. Jego celem jest modelowanie zapytań i poleceń zamiast posiadania modelu dla rekordów używanych w CRUD. W miarę, jak systemy stają się coraz bardziej złożone, wymagania dotyczące widoków są często bardziej złożone niż tylko pokazanie jednego rekordu lub garści rekordów, a zapytanie może lepiej modelować potrzeby aplikacji. Podobnie polecenia mogą reprezentować zmiany w wielu rekordach zamiast CRUD, które zmieniają pojedyncze rekordy. Martin Fowler ostrzega
Aby odpowiedzieć na twoje pytanie, CQRS nie powinno być pierwszym rozwiązaniem przy projektowaniu aplikacji, gdy CRUD jest odpowiedni. Nic w twoim pytaniu nie wskazało mi, że masz powód do korzystania z CQRS.
Jeśli chodzi o MediatR, jest to biblioteka wiadomości w trakcie przetwarzania, której celem jest oddzielenie żądań od obsługi żądań. Musisz ponownie zdecydować, czy poprawi to twój projekt korzystania z tej biblioteki. Osobiście nie jestem zwolennikiem przesyłania wiadomości w toku. Luźne sprzężenie można osiągnąć w prostszy sposób niż wysyłanie wiadomości, więc polecam zacząć od tego.
źródło
CQRS jest bardziej rzeczą do zarządzania danymi niż i nie ma tendencji do zbyt silnego upuszczania do warstwy aplikacji (lub domeny, jeśli wolisz, ponieważ najczęściej jest używana w systemach DDD). Z drugiej strony twoja aplikacja MVC jest aplikacją warstwy prezentacji i powinna być dość dobrze oddzielona od rdzenia zapytania / trwałości CQRS.
Kolejna rzecz warta odnotowania (biorąc pod uwagę porównanie domyślnej
Login
metody i chęci posiadania cienkich kontrolerów): nie przestrzegałbym domyślnych szablonów / kodu szablonu ASP.NET, ponieważ powinniśmy się martwić o najlepsze praktyki.Lubię też cienkie kontrolery, ponieważ są bardzo łatwe do odczytania. Każdy kontroler, który mam zwykle ma obiekt „usługi”, który łączy się z nim, co zasadniczo obsługuje logikę wymaganą przez kontroler:
Wciąż wystarczająco cienki, ale tak naprawdę nie zmieniliśmy sposobu działania kodu, po prostu przekaż obsługę metody serwisowej, która tak naprawdę nie służy żadnemu celowi poza tym, że czynności kontrolera są łatwe do strawienia.
Należy pamiętać, że ta klasa usług jest nadal odpowiedzialna za delegowanie logiki do modelu / aplikacji zgodnie z wymaganiami, to tak naprawdę tylko niewielkie rozszerzenie kontrolera, aby utrzymać porządek w kodzie. Metody serwisowe są również na ogół dość krótkie.
Nie jestem pewien, czy mediator zrobiłby coś koncepcyjnie odmiennego: przeniesienie podstawowej logiki kontrolera z kontrolera do innego miejsca do przetworzenia.
(Nie słyszałem wcześniej o tym MediatR, a szybkie spojrzenie na stronę github nie wydaje się wskazywać, że jest to coś przełomowego - na pewno nie coś takiego jak CQRS - w rzeczywistości wygląda to jak kolejna warstwa abstrakcji można wprowadzić, aby skomplikować kod, dzięki czemu wygląda on prostiej, ale to tylko moje początkowe podejście)
źródło
Bardzo polecam obejrzenie prezentacji NDC Jimmy'ego Bogarda na temat jego podejścia do modelowania zapytań http https://www.youtube.com/watch?v=SUiWfhAhgQw
Następnie uzyskasz jasne pojęcie o tym, do czego służy Mediatr.
Jimmy nie ślepo przestrzega wzorów i abstrakcji. On jest bardzo pragmatyczny. Mediatr usuwa działania kontrolera. Jeśli chodzi o obsługę wyjątków, wypycham to do klasy nadrzędnej o nazwie coś takiego jak Execute. W rezultacie otrzymujesz bardzo czyste działanie kontrolera.
Coś jak:
Sposób użycia wygląda trochę tak:
Mam nadzieję, że to pomaga.
źródło
Wiele osób (ja też to zrobiłem) myli wzór z biblioteką. CQRS to wzorzec, ale MediatR to biblioteka , której można użyć do wdrożenia tego wzorca
Możesz używać CQRS bez MediatR lub dowolnej biblioteki komunikatów w toku i możesz używać MediatR bez CQRS:
CQS wyglądałby tak:
W rzeczywistości nie musisz nazywać modeli wejściowych „Poleceniami” jak wyżej
CreateProductCommand
. I wprowadzanie zapytań „Zapytania”. Polecenia i zapytania to metody, a nie modele.CQRS polega na segregacji odpowiedzialności (metody odczytu muszą znajdować się w innym miejscu niż metody pisania - izolowane). Jest to rozszerzenie do CQS, ale różnica polega na tym, że można umieścić te metody w 1 klasie. (bez segregacji odpowiedzialności, po prostu separacja poleceń i zapytań). Zobacz rozdział a segregacja
Od https://martinfowler.com/bliki/CQRS.html :
Jest zamieszanie w tym, co mówi, nie chodzi o oddzielny model dla danych wejściowych i wyjściowych, chodzi o rozdzielenie odpowiedzialności.
CQRS i ograniczenie generowania identyfikatora
Podczas korzystania z CQRS lub CQS napotkasz jedno ograniczenie
Technicznie w oryginalnym opisie polecenia nie powinny zwracać żadnych wartości (void), które uważam za głupie, ponieważ nie ma łatwego sposobu na uzyskanie wygenerowanego identyfikatora z nowo utworzonego obiektu: /programming/4361889/how-to- get-id-in-create-when-application-cqrs .
więc musisz generować identyfikator za każdym razem, zamiast pozwolić bazie danych to zrobić.
Jeśli chcesz dowiedzieć się więcej: https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf
źródło