Ultimamente tenho pesquisado o CQRS / MediatR. Mas quanto mais eu detalhar, menos eu gosto. Talvez eu tenha entendido mal alguma coisa / tudo.
Então, ele começa incrível, alegando reduzir seu controlador a esse
public async Task<ActionResult> Edit(Edit.Query query)
{
var model = await _mediator.SendAsync(query);
return View(model);
}
O que se encaixa perfeitamente com as diretrizes finas do controlador. No entanto, deixa de fora alguns detalhes muito importantes - tratamento de erros.
Vamos analisar a Login
ação padrão de um novo projeto 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);
}
Convertendo isso nos apresenta um monte de problemas do mundo real. Lembre-se que o objetivo é reduzi-lo a
public async Task<IActionResult> Login(Login.Command command, string returnUrl = null)
{
var model = await _mediator.SendAsync(command);
return View(model);
}
Uma solução possível para isso é retornar um em CommandResult<T>
vez de um model
e depois manipular o CommandResult
filtro em uma ação pós. Como discutido aqui .
Uma implementação do CommandResult
poderia ser assim
public interface ICommandResult
{
bool IsSuccess { get; }
bool IsFailure { get; }
object Result { get; set; }
}
No entanto, isso realmente não resolve o nosso problema na Login
ação, porque existem vários estados de falha. Poderíamos adicionar esses estados extras de falha, ICommandResult
mas isso é um ótimo começo para uma classe / interface muito inchada. Pode-se dizer que não está de acordo com a responsabilidade única (SRP).
Outro problema é o returnUrl
. Nós temos esse return RedirectToLocal(returnUrl);
pedaço de código. De alguma forma, precisamos lidar com argumentos condicionais com base no estado de sucesso do comando. Embora eu ache que isso possa ser feito (não tenho certeza se o ModelBinder pode mapear os argumentos FromBody e FromQuery ( returnUrl
é FromQuery) para um único modelo). Só podemos imaginar que tipo de cenários malucos poderiam surgir no caminho.
A validação do modelo também se tornou mais complexa junto com o retorno de mensagens de erro. Tome isso como um exemplo
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
Anexamos uma mensagem de erro junto com o modelo. Esse tipo de coisa não pode ser feito usando uma Exception
estratégia (como sugerido aqui ) porque precisamos do modelo. Talvez você possa obter o modelo do Request
mas seria um processo muito envolvido.
Então, apesar de tudo, estou tendo dificuldade para converter essa ação "simples".
Eu estou procurando por entradas. Estou totalmente errado aqui?
fonte
Respostas:
Eu acho que você está esperando muito do padrão que está usando. O CQRS foi projetado especificamente para resolver a diferença de modelo entre consulta e comandos no banco de dados , e o MediatR é apenas uma biblioteca de mensagens em processo. O CQRS não pretende eliminar a necessidade de lógica de negócios como você espera. O CQRS é um padrão para acesso a dados, mas seus problemas estão na camada de apresentação - redirecionamentos, visualizações, controladores.
Eu acho que você pode estar aplicando incorretamente o padrão CQRS à autenticação. Com o login, ele não pode ser modelado como um comando no CQRS porque
Na minha opinião, a autenticação é um domínio ruim para o CQRS. Com a autenticação, você precisa de um fluxo de solicitação e resposta síncrono e altamente consistente, para que você possa 1. verificar as credenciais do usuário 2. criar uma sessão para o usuário 3. lidar com qualquer uma das várias situações de ponta que você identificou 4. conceder ou negar imediatamente o usuário em resposta.
CQRS é um padrão que possui usos muito específicos. Seu objetivo é modelar consultas e comandos em vez de ter um modelo para registros, conforme usado no CRUD. À medida que os sistemas se tornam mais complexos, as demandas de visualizações geralmente são mais complexas do que apenas mostrar um único registro ou um punhado de registros, e uma consulta pode modelar melhor as necessidades do aplicativo. Da mesma forma, os comandos podem representar alterações em muitos registros, em vez de CRUD, que você altera registros únicos. Martin Fowler adverte
Portanto, para responder à sua pergunta, o CQRS não deve ser o primeiro recurso ao projetar um aplicativo quando o CRUD for adequado. Nada na sua pergunta me deu a indicação de que você tem um motivo para usar o CQRS.
Quanto ao MediatR, é uma biblioteca de mensagens em processo, que visa dissociar solicitações do tratamento de solicitações. Você deve decidir novamente se ele melhorará seu design para usar esta biblioteca. Pessoalmente, não sou um defensor das mensagens em processo. O acoplamento flexível pode ser alcançado de maneiras mais simples que as mensagens, e eu recomendo que você comece por aí.
fonte
O CQRS é mais uma coisa de gerenciamento de dados e não tende a sangrar muito em uma camada de aplicativo (ou Domínio, se preferir, pois costuma ser usado com mais freqüência em sistemas DDD). Seu aplicativo MVC, por outro lado, é um aplicativo de camada de apresentação e deve estar razoavelmente bem separado do núcleo de consulta / persistência do CQRS.
Outra coisa que vale a pena notar (dada a comparação do
Login
método padrão e o desejo de controladores thin): eu não seguiria exatamente o código padrão de modelos / clichê do ASP.NET como algo que nos preocuparia com as práticas recomendadas.Também gosto de controladores finos, porque são muito fáceis de ler. Cada controlador que eu tenho geralmente tem um objeto de "serviço" que ele emparelha com o que lida essencialmente com a lógica exigida pelo controlador:
Ainda é fino o suficiente, mas não mudamos realmente o funcionamento do código, apenas delegamos a manipulação ao método de serviço, que realmente não serve para nada além de facilitar a digestão das ações do controlador.
Lembre-se de que essa classe de serviço ainda é responsável por delegar a lógica ao modelo / aplicativo, conforme necessário; na verdade, é apenas uma pequena extensão do controlador para manter o código limpo. Os métodos de serviço também são geralmente bastante curtos.
Não sei se o mediador faria algo conceitualmente diferente: mover alguma lógica básica do controlador para fora do controlador e para outro lugar a ser processado.
(Eu nunca tinha ouvido falar desse MediatR antes, e uma rápida olhada na página do github não parece indicar que é algo inovador - certamente não algo como o CQRS - na verdade, parece algo como apenas mais uma camada de abstração. pode colocar para complicar o código, tornando-o mais simples, mas essa é apenas a minha opinião inicial)
fonte
Eu recomendo que você veja a apresentação da NDC de Jimmy Bogard sobre sua abordagem para modelar solicitações http https://www.youtube.com/watch?v=SUiWfhAhgQw
Você terá uma idéia clara do uso do Mediatr.
Jimmy não tem uma adesão cega a padrões e abstrações. Ele é muito pragmático. O Mediatr limpa as ações do controlador. Quanto ao tratamento de exceções, eu envio isso para uma classe pai chamada algo como Execute. Então você acaba com uma ação de controlador muito limpa.
Algo como:
O uso se parece um pouco com isso:
Espero que ajude.
fonte
Muitas pessoas (eu também fiz) confundem padrão com uma biblioteca. CQRS é um padrão, mas o MediatR é uma biblioteca que você pode usar para implementar esse padrão
Você pode usar o CQRS sem o MediatR ou qualquer biblioteca de mensagens em processo e o MediatR sem o CQRS:
O CQS ficaria assim:
Na verdade, você não precisa nomear seus modelos de entrada como "Comandos", como acima
CreateProductCommand
. E entrada de suas consultas "Consultas". Comando e consultas são métodos, não modelos.O CQRS trata da segregação de responsabilidades (os métodos de leitura devem estar em um local separado dos métodos de gravação - isolados). É uma extensão do CQS, mas a diferença está no CQS. Você pode colocar esses métodos em 1 classe. (sem segregação de responsabilidades, apenas separação de comando e consulta). Veja separação vs segregação
Em https://martinfowler.com/bliki/CQRS.html :
Há confusão no que diz, não se trata de ter um modelo separado para entrada e saída, é sobre separação de responsabilidades.
CQRS e limitação de geração de ID
Há uma limitação que você enfrentará ao usar o CQRS ou CQS
Tecnicamente, na descrição original, os comandos não devem retornar nenhum valor (vazio) que eu acho estúpido, porque não há uma maneira fácil de obter a ID gerada de um objeto recém-criado: /programming/4361889/how-to- get-id-in-create-when-apply-cqrs .
então você precisa gerar um ID cada vez, em vez de permitir que o banco de dados faça isso.
Se você quiser saber mais: https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf
fonte