Por que separar a classe CommandHandler com Handle () em vez de manipular o método no próprio Comando

13

Eu tenho uma parte do padrão CQRS implementada usando a arquitetura S # arp como esta:

public class MyCommand
{
    public CustomerId { get; set; }

    // some other fields
}

public class MyCommandHandler<MyCommand> : ICommandHandler<MyCommand, CommandResult>
{
    Handle(MyCommand command)
    {
        // some code for saving Customer entity

        return CommandResult.Success;
    }
}

Gostaria de saber por que não apenas ter classe Commandcontendo dados e método de manipulação? É um tipo de benefício de testabilidade, no qual você precisa testar a lógica de manipulação de comandos separadamente das propriedades do comando? Ou é algum requisito comercial frequente, onde você precisa ter um comando tratado por diferentes implementações de ICommandHandler<MyCommand, CommandResult>?

rgripper
fonte
Eu tinha a mesma pergunta, vale a pena procurar: blogs.cuttingedge.it/steven/posts/2011/…
rdhaundiyal

Respostas:

14

Engraçado, essa pergunta me lembrou exatamente a mesma conversa que tive com um de nossos engenheiros sobre a biblioteca de comunicações em que estava trabalhando.

Em vez de comandos, eu tinha classes Request e, em seguida, tinha RequestHandlers. O design era muito parecido com o que você está descrevendo. Eu acho que parte da confusão que você tem é que você vê a palavra em inglês "comando" e pensa instantaneamente em "verbo, ação ... etc".

Mas neste design, pense em Command (ou Request) como uma carta. Ou para aqueles que não sabem o que é um serviço postal, pense no e-mail. É simplesmente conteúdo, dissociado de como esse conteúdo deve ser usado.

Por que você faria isso? Na maioria dos casos simples, do Padrão de Comando não há razão e você pode fazer com que essa classe execute o trabalho diretamente. No entanto, fazer a dissociação como no seu design faz sentido se sua ação / comando / solicitação precisar percorrer alguma distância. Por exemplo, entre, soquetes ou tubulações, ou entre domínio e infraestrutura. Ou talvez na sua arquitetura seus comandos precisem ser persistentes (por exemplo, o manipulador de comandos pode executar 1 comando por vez, devido a alguns eventos do sistema, 200 comandos chegam e após os primeiros 40 processos serem desligados). Nesse caso, tendo uma classe simples de mensagem, torna-se muito simples serializar apenas a parte da mensagem em JSON / XML / binário / qualquer que seja e transmiti-la pelo pipeline até que seu manipulador de comandos esteja pronto para processá-la.

Outra vantagem da dissociação do Command do CommandHandler é que agora você tem a opção de hierarquia de herança paralela. Por exemplo, todos os seus comandos podem derivar de uma classe de comando base que suporta serialização. E talvez você tenha 4 dos 20 manipuladores de comando que têm muita semelhança, agora você pode derivar aqueles da classe base do manipulador fornecido. Se você tivesse manipulação de dados e comandos em uma classe, esse tipo de relacionamento rapidamente sairia do controle.

Outro exemplo para a dissociação seria se o seu comando exigisse muito pouca entrada (por exemplo, 2 números inteiros e uma string), mas sua lógica de manipulação fosse complexa o suficiente para que você desejasse armazenar dados nas variáveis ​​de membro intermediárias. Se você enfileirar 50 comandos, não deseja alocar memória para todo esse armazenamento intermediário, para separar Command e CommandHandler. Agora você enfileira 50 estruturas de dados leves e o armazenamento de dados mais complexo é alocado apenas uma vez (ou N vezes se você tiver N manipuladores) pelo CommandHandler que está processando os comandos.

DXM
fonte
O ponto é que, nesse contexto, o comando / solicitação não é remoto / persistente / etc. É tratado diretamente. E não vejo como separar os dois ajudaria na herança. Isso realmente tornaria mais difícil. O último parágrafo também é uma espécie de falta. A criação de objetos não é uma operação cara e 50 comandos são um número negligenciável.
Euphoric
@Euphoric: como você sabe qual é o contexto? A menos que a arquitetura S # arp seja algo especial, tudo o que vejo são algumas declarações de classe e você não tem idéia de como elas são usadas no restante do aplicativo. Se você não gosta dos números que escolhi como 50, escolha algo como 50 por segundo. Se isso não for suficiente, escolha 1000 por segundo. Eu só estava tentando fornecer exemplos. Ou você não acha que nesse contexto ele terá tantos comandos?
DXM
Por exemplo, a estrutura exata é vista aqui weblogs.asp.net/shijuvarghese/archive/2011/10/18/… . E em nenhum lugar lá diz o que você disse. E sobre a velocidade, o problema é que você usou o argumento 'desempenho' sem criar um perfil. Se você possui requisitos para essa taxa de transferência, não usará a arquitetura genérica, mas criará algo mais especializado.
Euphoric
1
Deixe-me ver se esse foi o seu último ponto: o OP pediu exemplos, e eu deveria ter dito, por exemplo, primeiro você projeta da maneira mais simples e seu aplicativo funciona, depois você aumenta a escala e amplia os lugares onde usa o padrão de comando e, em seguida, você entra no ar e obtém 10.000 máquinas conversando com seu servidor, e o servidor ainda usa sua arquitetura original; depois, cria um perfil e identifica o problema; depois, você pode separar os dados de comando da manipulação de comandos, mas somente depois de criar um perfil. Seria realmente mais feliz se eu incluísse tudo isso na resposta? Ele pediu um exemplo, eu dei um a ele.
DXM
... então apenas olhei através do post que você postou e parece alinhado com o que escrevi: separe-os se o seu comando precisar percorrer alguma distância. No blog, ele parece estar se referindo a um barramento de comando, que é basicamente apenas outro canal, soquete, fila de mensagens, esb ... etc #
DXM
2

O padrão de comando normal é sobre ter os dados e o comportamento em uma única classe. Esse tipo de 'padrão de comando / manipulador' é um pouco diferente. A única vantagem comparada ao padrão normal é a vantagem adicional de não ter seu comando dependente das estruturas. Por exemplo, seu comando pode precisar de acesso ao banco de dados, portanto, ele precisa ter algum tipo de contexto ou sessão de banco de dados, o que significa que depende das estruturas. Mas esse comando pode fazer parte do seu domínio, portanto você não deseja que ele dependa de estruturas de acordo com o Princípio de Inversão de Dependência . Separar os parâmetros de entrada e saída do comportamento e ter algum despachante para conectá-los pode corrigir isso.

Por outro lado, você perderá vantagem da herança e da composição dos comandos. O que eu acho que é o verdadeiro poder.

Além disso, nitpick menor. Só porque ele tem Command no nome, não faz parte do CQRS. Isso é algo muito mais fundamental. Esse tipo de estrutura pode servir como comando e como consulta, mesmo ao mesmo tempo.

Eufórico
fonte
Eu vi o link weblogs.asp.net/shijuvarghese/archive/2011/10/18/… que você apontou, mas não vejo nenhum sinal de barramento no código S # arp Arch que tenho. Então, eu acho que essa separação no meu caso apenas espalha classes e espirra a lógica.
precisa saber é o seguinte
Hmm, obrigado por apontar. Então, meu caso é ainda pior, porque no código que eu tenho ICommandProcessor é COI e resolvido para CommandProcessor (que por si só está criando um COI para os manipuladores de comando) - uma composição enlameada. E no projeto parece não haver casos de negócios para mais de um hadler para um comando.
rgripper