Architecture-center: Qual é a diferença entre ProductsCommandHandler (CQRS) e ProductRepository (arco tradicional)?

Criado em 15 nov. 2019  ·  5Comentários  ·  Fonte: MicrosoftDocs/architecture-center

Oi,
Obrigado por escrever este artigo sobre CQRS. Estou tentando entender como é a implementação e examinando os exemplos. O código de repositório de amostra fornecido no exemplo é muito semelhante a como normalmente se escreveria um repo, exceto pela convenção de nomenclatura. Por exemplo, acho que o repositório dado abaixo é o mesmo que ProductsCommandHandler e provavelmente uma diferença seria que não há GetProduct aqui que normalmente adicionaríamos. Você poderia explicar o que não estou conseguindo aqui?

public class ProductRepository {
  void AddNewProduct(Product newProduct) {
    ...
  }
  void RateProduct(int productId, int userId, int rating) {
    var product = repository.Find(productId);
    if (product != null)
    {
      product.RateProduct(userId, rating);
      repository.Save(product);
    }
  }
}

Detalhes do Documento

Não edite esta seção.

Pri1 architecture-centesvc assigned-to-author cloud-fundamentalsubsvc product-question triaged

Comentários muito úteis

@martinmthomas Na verdade, o manipulador de comandos não é um repositório. Ele usa um repositório.

O manipulador de comandos destina-se a "processar os comandos reais, se adequado". No exemplo, a classe ProductsCommandHandler recebe um IRepository<Product> em seu construtor.

Digamos que o usuário avalie o produto 5555 com 4 estrelas.

  • O usuário vê uma interface. Digamos que uma página da web (pode ser um programa .exe local ou qualquer outro, vamos imaginar um site).
  • O usuário clica em um botão. Digamos que isso acione um AJAX POST indo para uma rota / taxa com dados {"product":"5555","stars":4}
  • A rota POST possui um controlador. Este controlador está associado a uma operação de "escrita" visto que se trata de uma chamada POST.
  • O controlador aqui em uma abordagem clássica carregaria apenas o repositório do produto clássico e carregaria o produto 5555. Em seguida, diga ao produto "agora você está classificado com 4 estrelas" e salve.
  • Nessa abordagem, o controlador cria o comando RateProduct e preenche o produto 5555, com estrelas 4. No exemplo, ele também preenche quem está avaliando.
  • Nesse momento, o controlador "envia o comando" para o modelo de gravação. Você tem 2 opções aqui: Chamar um manipulador de comando ou colocá-lo na fila.
  • Digamos que você não tenha fila. Você então obtém CommandHandler em seu controlador (provavelmente por injeção de dependência) e apenas coloca o comando lá: h.Handle (c); onde c é o comando RateProduct.
  • Digamos que você tenha uma fila. Você então obtém a fila em seu controlador (provavelmente por injeção de dependência) e apenas enfileira o comando lá: q.Queue (c);
  • Neste último caso, o ouvinte da fila tirará o comando da fila e chamará o manipulador de comandos.
  • Como opção terceira (recomendada), seu controlador não escolhe se o comando está sendo processado de forma síncrona (obter o manipulador) ou de forma assíncrona (obter a fila), mas deve usar uma "abordagem genérica" ​​que é usar um barramento de comando onde os comandos é colocado. Digamos que o barramento de comando seja b, então você faria b.Send (c); onde c é o comando RateProduct.
  • Com esta 3ª abordagem, você coloca o manipulador de comandos no final do barramento e pode configurar middlewares para "retirar o comando do barramento e colocá-lo na fila".

Portanto, 3 opções: a) Pegue o manipulador, b) Pegue a fila, c) Pegue um ônibus e deixe-o decidir enviar o comando para o manipulador ou para a fila.

Seja qual for a abordagem que você fizer ... a principal diferença sobre "um repositório clássico" é que você NUNCA invoca o próprio repositório no controlador. O controlador não "sabe como gerenciar entidades" (repositório clássico), mas sabe "como gerenciar INTENÇÕES DE FAZER COISAS para entidades" (manipulador de comando).

Então é o manipulador de comandos que -como o nome diz- ele "lida com o comando" que veio de algum lugar (um controlador da web html, um controlador de API, uma linha de comando, qualquer coisa) que foi enviado como um intent e é o CommandHandler (que pertence ao lado da gravação) que decide o que fazer com aquele comando.

Por exemplo, o CommandHandler pode usar um repo para obter o produto, definir o estado e salvar (como no exemplo), mas também pode gravar em um log de eventos, acionar elementos para atualizar os lados de leitura, acionar conectores externos ou qualquer outra coisa.

A IU baseada em tarefas do usuário para classificar o produto e seu controlador como AGNÓSTICO de "onde" o sistema de estrelas / classificação é colocado ou armazenado. Imagine que você projeta um sistema do zero e a classe Product já contém o método RateProduct () como no exemplo. Bom.

Mas ... e se você tiver um sistema legado com uma abordagem "Antiga" para o Produto. No "modelo" (ou seja: Business Mind) não há "classificação" do produto. Em vez disso, o cara de marketing quer "adicionar" classificação ao produto existente, mas toda a empresa concorda que isso é uma "coisa externa". Você usaria o repositório do produto? Ou talvez outro armazenamento auxiliar para que você não "toque" no Produto e no Repositório de Produtos já testados e em funcionamento?

Se você usar comandos, isso não importa para o controlador de gravação. A web, a API e a CLI irão apenas enviar o "comando" para o manipulador de comandos (seja diretamente, via fila ou por meio de um barramento de comando que irá rotear por sua vez para o manipulador ou para uma fila) e eles esquecerão. Em seguida, o manipulador de comandos decidirá o que fazer com o "RateProductCommand" em um ponto centralizado e pequeno de seu código-fonte, isso desvinculando a forma como isso é tratado do código da aplicação, ganhando assim capacidade de manutenção.

Em seguida, o manipulador decidirá se é adequado usar o ProductRepository ou qualquer outra abordagem para armazenar "a classificação de um produto".

Então, para responder:

CommandHandler => não tem nada a ver com os direitos. Ele lida com as "intenções do usuário" (que, em última análise, podem ser alterar entidades; portanto, muito provavelmente o manipulador de comandos usa um repositório).
Repositório => o armazenamento real para uma determinada entidade.

Espero ajudar.
Xavi.

Todos 5 comentários

@martinmthomas Obrigado pela sua pergunta! Iremos revisar e fornecer uma atualização conforme apropriado.

@MikeWasson alguma opinião aqui?

AB # 160217 - Obrigado por relatar - este problema está em revisão

@martinmthomas Na verdade, o manipulador de comandos não é um repositório. Ele usa um repositório.

O manipulador de comandos destina-se a "processar os comandos reais, se adequado". No exemplo, a classe ProductsCommandHandler recebe um IRepository<Product> em seu construtor.

Digamos que o usuário avalie o produto 5555 com 4 estrelas.

  • O usuário vê uma interface. Digamos que uma página da web (pode ser um programa .exe local ou qualquer outro, vamos imaginar um site).
  • O usuário clica em um botão. Digamos que isso acione um AJAX POST indo para uma rota / taxa com dados {"product":"5555","stars":4}
  • A rota POST possui um controlador. Este controlador está associado a uma operação de "escrita" visto que se trata de uma chamada POST.
  • O controlador aqui em uma abordagem clássica carregaria apenas o repositório do produto clássico e carregaria o produto 5555. Em seguida, diga ao produto "agora você está classificado com 4 estrelas" e salve.
  • Nessa abordagem, o controlador cria o comando RateProduct e preenche o produto 5555, com estrelas 4. No exemplo, ele também preenche quem está avaliando.
  • Nesse momento, o controlador "envia o comando" para o modelo de gravação. Você tem 2 opções aqui: Chamar um manipulador de comando ou colocá-lo na fila.
  • Digamos que você não tenha fila. Você então obtém CommandHandler em seu controlador (provavelmente por injeção de dependência) e apenas coloca o comando lá: h.Handle (c); onde c é o comando RateProduct.
  • Digamos que você tenha uma fila. Você então obtém a fila em seu controlador (provavelmente por injeção de dependência) e apenas enfileira o comando lá: q.Queue (c);
  • Neste último caso, o ouvinte da fila tirará o comando da fila e chamará o manipulador de comandos.
  • Como opção terceira (recomendada), seu controlador não escolhe se o comando está sendo processado de forma síncrona (obter o manipulador) ou de forma assíncrona (obter a fila), mas deve usar uma "abordagem genérica" ​​que é usar um barramento de comando onde os comandos é colocado. Digamos que o barramento de comando seja b, então você faria b.Send (c); onde c é o comando RateProduct.
  • Com esta 3ª abordagem, você coloca o manipulador de comandos no final do barramento e pode configurar middlewares para "retirar o comando do barramento e colocá-lo na fila".

Portanto, 3 opções: a) Pegue o manipulador, b) Pegue a fila, c) Pegue um ônibus e deixe-o decidir enviar o comando para o manipulador ou para a fila.

Seja qual for a abordagem que você fizer ... a principal diferença sobre "um repositório clássico" é que você NUNCA invoca o próprio repositório no controlador. O controlador não "sabe como gerenciar entidades" (repositório clássico), mas sabe "como gerenciar INTENÇÕES DE FAZER COISAS para entidades" (manipulador de comando).

Então é o manipulador de comandos que -como o nome diz- ele "lida com o comando" que veio de algum lugar (um controlador da web html, um controlador de API, uma linha de comando, qualquer coisa) que foi enviado como um intent e é o CommandHandler (que pertence ao lado da gravação) que decide o que fazer com aquele comando.

Por exemplo, o CommandHandler pode usar um repo para obter o produto, definir o estado e salvar (como no exemplo), mas também pode gravar em um log de eventos, acionar elementos para atualizar os lados de leitura, acionar conectores externos ou qualquer outra coisa.

A IU baseada em tarefas do usuário para classificar o produto e seu controlador como AGNÓSTICO de "onde" o sistema de estrelas / classificação é colocado ou armazenado. Imagine que você projeta um sistema do zero e a classe Product já contém o método RateProduct () como no exemplo. Bom.

Mas ... e se você tiver um sistema legado com uma abordagem "Antiga" para o Produto. No "modelo" (ou seja: Business Mind) não há "classificação" do produto. Em vez disso, o cara de marketing quer "adicionar" classificação ao produto existente, mas toda a empresa concorda que isso é uma "coisa externa". Você usaria o repositório do produto? Ou talvez outro armazenamento auxiliar para que você não "toque" no Produto e no Repositório de Produtos já testados e em funcionamento?

Se você usar comandos, isso não importa para o controlador de gravação. A web, a API e a CLI irão apenas enviar o "comando" para o manipulador de comandos (seja diretamente, via fila ou por meio de um barramento de comando que irá rotear por sua vez para o manipulador ou para uma fila) e eles esquecerão. Em seguida, o manipulador de comandos decidirá o que fazer com o "RateProductCommand" em um ponto centralizado e pequeno de seu código-fonte, isso desvinculando a forma como isso é tratado do código da aplicação, ganhando assim capacidade de manutenção.

Em seguida, o manipulador decidirá se é adequado usar o ProductRepository ou qualquer outra abordagem para armazenar "a classificação de um produto".

Então, para responder:

CommandHandler => não tem nada a ver com os direitos. Ele lida com as "intenções do usuário" (que, em última análise, podem ser alterar entidades; portanto, muito provavelmente o manipulador de comandos usa um repositório).
Repositório => o armazenamento real para uma determinada entidade.

Espero ajudar.
Xavi.

Como @xmontero mencionou, ProductsCommandHandler e ProductRepository tem apenas relação "tem". ProductsCommandHandler atua como interface entre ProductApi / Controller e ProductRepository. Essa implementação ajuda a manter os comandos juntos separados das consultas. Na abordagem tradicional, usamos diretamente o repositório do controlador, mantendo a execução de comandos e consultas juntos, portanto, não há necessidade de qualquer outra interface entre eles.

Você aplicou o padrão de design CQRS em seu aplicativo, se mantiver os Comandos separados das Consultas. Leva você na direção de obter todos os benefícios do CQRS mencionados nos artigos.

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

IjsConsulting picture IjsConsulting  ·  6Comentários

marcusturewicz picture marcusturewicz  ·  3Comentários

t2kx picture t2kx  ·  4Comentários

bagira-kr picture bagira-kr  ·  8Comentários

CloudLassoUK picture CloudLassoUK  ·  7Comentários