Back-End

1 jun, 2016

Command Bus e Action-Domain-Responder

Publicidade

Ao longo das últimas semanas, diferentes pessoas têm me perguntado onde um Command Bus vai em um sistema de Action-Domain-Responder. Apesar de eu não ser um especialista em DDD, depois de estudar o assunto um pouco, minha resposta é: “No domínio”.

Primeiro, vamos recordar os três componentes em ADR:

  • “Action” é a lógica que liga Domain e Responder. Ela usa a entrada do pedido para interagir com Domain, e passa a saída de domínio para o dispositivo de Resposta. (A Action é intencionalmente “burra”: ela não deve ter nenhuma lógica, com exceção de o mínimo de ternários para permitir valores de entrada padrão. Se a Action tem uma condicional, ela está fazendo muito.)
  • “Domain” é a lógica para manipular os dados de domínio, de sessão, aplicativo e ambiente, modificando o estado e a persistência, conforme necessário. (A palavra “Domain” aqui é explicitamente intencionada para lembrar você de “lógica de domínio” e “Domain-Driven Design”).
  • “Responder” é a lógica para construir uma resposta HTTP ou descrição de resposta. Ele trata do conteúdo do corpo, templates e views, cabeçalhos e os cookies, códigos de status, e assim por diante.

Em seguida, vamos ver o que é Command Bus. Há muito escrito sobre ele em todos os lugares…

… então eu vou tentar resumir aqui:

  • Um “Command” é um conjunto digitado ou chamado de inputs (essencialmente um objeto de transferência de dados) que é enviado a um “Command Bus”.
  • O “Command Bus”, em seguida, passa o “Command” para um “Command Handler” específico para esse “Command”; o “Command Bus” descobre que “Command Handler” usar a partir do nome ou tipo do “Command”.
  • O “Command Handler” usa as entradas do “Command” para realizar algum tipo de atividade.

Esta série de objetos é parte de um padrão de arquitetura abrangente chamado Command Query Responsibility Segregation (veja a respeito aqui e aqui). Sob CQRS, escritas (Commands) são tratadas separadamente de leituras (Queries). Manipular um Command modifica os dados, mas não retorna um resultado, enquanto manusear uma Query retorna um resultado, mas não deve modificar dados.

Isso significa que um Command Bus, na verdade, não retorna um resultado de inspeção. Você despeja um Command para o Bus, e pronto; não há nenhuma verificação de erros nesse momento. Para estar em conformidade correta com o CQRS, você tem que executar uma Query em separado, a fim de determinar o resultado do Command.

Nesse ponto, apenas por ter lido a literatura sobre padrões e conceitos, podemos ver que o Command Bus e seus componentes relacionados são parte da camada de Domain, e não fazem parte da camada de interface do usuário. Com isso em mente, parece que Command Bus é um candidato para a parte “Domain” de Açtion-Domain-Responder, para ser usado como isto em uma Action:

class CreateItemAction
{
    public function __construct(
        CommandBus $domain,
        CommandResponder $responder
    ) {
        $this->domain = $domain;
        $this->responder = $responder;
    }

    public function __invoke(Request $request)
    {
        $input = $request->getParsedBody();
        $command = new CreateItemCommand(
            $input['item_name'],
            $input['item_description']
        );
        $this->domain->handle($command);
        return $this->responder->createResponse();
    }
}

Assim, a Action começa construída com um elemento CommandBus como ponto de entrada para o Domain, e com um Responder genérico para construir a resposta. Em tempo de invocação, o código de interface do usuário envia ao longo da solicitação HTTP atual; Action puxa os dados de fora para criar Command para enviar para o Command Bus, então, diz ao Responder para criar uma resposta HTTP. (Porque um Command nunca retorna nada, um Responder deve ser suficiente para todos os Commands dessa configuração.)

Este é um caso mínimo, mas eu acho que evita pelo menos duas questões substanciais.

  1. Onde vai a validação de entrada? (Validação de entrada, ou validação de formulário, é separada da validação do modelo de domínio.)
  2. Onde vai o tratamento de erros? (Enquanto um Command pode não retornar nada, os vários elementos relacionados com CQRS podem muito bem lançar exceções ou gerar erros.)

Se pensarmos como um servidor MVC, essas duas preocupações poderiam muito bem ser colocadas em algum local no Controller. Traduzindo um método Controller diretamente para uma Action, que poderia ser algo como isto:

public function __invoke(Request $request)
    {
        // obter a entrada e validar
        $input = $request->getParsedBody();
        if (! $this->validate($input)) {
            // criar uma resposta de "entrada inválida"
            return $this->responder->invalid($input);
        }

        // criar o comando
        $command = new CreateItemCommand(
            $input['item_name'],
            $input['item_description']
        );

        // testar o comando
        try {
            $this->domain->handle($command);
            // sucesso!
            return $this->responder->success();
        } catch (Exception $e) {
            // houve algum tipo de falha subsequente
            return $this->responder->failure($e);
        }
    }

Em consideração, isso parece ser um monte de atividades irrelevantes na camada de interface do usuário. Em ADR, a Action intencionalmente deve ser burra. Ela não deve estar fazendo algo remotamente interessante, e certamente não deve lidar com qualquer lógica condicional.

Assim, eu digo que a atividade relacionada ao Command deve ser retirada da Action inteiramente, e relegada a algo como um Application Service ou algum outro ponto de entrada do Domain. Tal Service é o que deve executar a atividade relacionada ao Command.

Além disso, enquanto um Command não deve retornar um resultado, um Service certamente pode. Isso significa que a Action pode chamar o Domain e receber de volta uma Domain Payload como resultado, que pode então ser passada para o Responder para a apresentação.

Sob essa maneira de pensar, temos algo mais parecido com isto:

class CreateItemAction
{
    public function __construct(
        ItemService $domain,
        CreateItemResponder $responder
    ) {
        $this->domain = $domain;
        $this->responder = $responder;
    }

    public function __invoke(Request $request)
    {
        $input = $request->getParsedBody();
        $payload = $this->domain->create($input);
        return $this->responder->createResponse($payload);
    }
}

class ItemService
{
    public function create(array $input)
    {
        if (! $this->validate($input)) {
            // cria uma resposta de "entrada inválida"
            return new InvalidInputPayload($input);
        }

        // cria o comando
        $command = new CreateItemCommand(
            $input['item_name'],
            $input['item_description']
        );

        // teste o comando
        try {
            $this->domain->handle($command);
            return new CommandAcceptedPayload();
        } catch (Exception $e) {
            return new CommandRejectedPayload($e);
        }
    }
}

Agora, o Domain é totalmente separado da interface do usuário. Pode ser usado tanto com HTTP e interfaces de linha de comando, e testado separadamente a partir deles. Por padrão ADR, o Responder é responsável pela análise do Payload para ver que tipo de apresentação entregar para o cliente. Diferentes Payloads resultam em diferentes respostas sendo construídas. Finalmente, a Action se torna totalmente desinteressante; toda a lógica de negócios tem sido empurrada para dentro do Domain, onde ela pertence.

Então, para resumir: Command Bus é um padrão de camada de Domain, e deve ser utilizado no Domain, não na Action. Um Command não pode retornar um resultado, mas um Service pode, por isso o ponto de entrada para o Domain é provavelmente melhor como um Service que retorna uma Payload. Isso mantém o HTTP e a lógica de interface de usuário CLI bem separada da lógica de negócios, e independentemente testável e reutilizável.

***

Paul M. Jones faz parte do time de colunistas internacionais do iMasters. A tradução do artigo é feita pela redação iMasters, com autorização do autor, e você pode acompanhar o artigo em inglês no link: http://paul-m-jones.com/archives/6268