Imaster

WikiMedia, arquitetura limpa e ADR

ADR, do inglês Action-Domain-Responder, é um ajuste natural para a interface de usuário HTTP destinada a uma arquitetura limpa (ou hexagonal), especialmente com o Domain Driven Design. Apenas se certifique de separar o HTTP de apresentação do código de ação.

I.

Jeroen de Dauw escreveu um artigo fantástico sobre a implementação de arquitetura limpa (?) em PHP, com os elementos do Design Orientado a Domínio. Vocês deveriam ler o artigo inteiro, e examinar o código da implementação, para ver algumas informações úteis. Embora eu possa criticar alguns elementos da implementação, eu acho que é uma boa oferta, e serve como um ponto de referência sólido.

Em seu artigo, Jeroen diz que eles estão utilizando o Silex para seu sistema de interface de usuário em HTML, e descreve a lógica de cada rota de ação:

Dentro disso [ação Silex], nós construímos nosso framework agnóstico ao modelo de solicitação e invocamos o caso de uso com ele. Então, nós entregamos o modelo de resposta para um apresentador criar o HTML apropriado ou outro formato.

Essa é uma paráfrase muito próxima do Action-Domain-Responder:

  • O Action organiza as entradas da solicitação HTML;
  • O Action invoca um elemento Domain com essas entradas e recebe o resultado;
  • O Action envia o resultado para o Responder construir a resposta em HTML.

Na implementação do Jeroen, cada Action é uma terminação definida no arquivo “routes.php”. A Action organiza a entrada da solicitação HTML utilizando um “modelo de solicitação”(um objeto de entrada feito sob medida para o domínio) e o encaminha para um “caso de uso”. Cada “caso de uso” é um ponto de entrada para o Domain, e retorna um “modelo de resposta”(o resultado do domínio).

O único ponto em que a implementação do Jeroen se difere do ADR é que o código da Action constrói a apresentação, em vez de enviar para um Responder (esse pode ser o resultado de aderir a linguagens e expectativas específicas do Silex).

Pelo resto da implementação estar muito bem feita, refatorar para uma apresentação separada utilizando um Responder é um exercício muito simples. Vamos ver como poderia ficar.

II.

Primeiro, como exemplo, reveja o código na ação check-iban. A seguinte reorganização daquele código de ação torna o padrão ADR mais óbvio:

<?php
$app->get(
    'check-iban',
    function( Request $request ) use ( $app, $ffFactory ) {

        // marshal input
        $input = new Iban( $request->query->get( 'iban', '' ) );

        // invoke domain and get back result
        $result = $ffFactory->newCheckIbanUseCase()->checkIban($input);

        // presentation
        return $app->json(
            $ffFactory->newIbanPresenter()->present( $result )
        );
    }
);
?>

Muito claro e simples. No entanto, o trabalho de apresentação está embutido na ação com a chamada $app→ json(…) (meu palpite é que isso é provavelmente um resultado do trabalho com as linguagens existentes no Silex).

Outro bom exemplo é a ação list-comments.html. Reorganizando a lógica para tornar o padrão ADR mais óbvio, nos dá o seguinte:

<?php
$app->get(
    'list-comments.html',
    function( Request $request ) use ( $app, $ffFactory ) {

        // marshal input
        $input = new CommentListingRequest(
            10,
            (int)$request->query->get( 'page', '1' )
        );

        // invoke domain and get back result
        $result = $ffFactory
            ->newListCommentsUseCase()
            ->listComments( $input );

        // presentation
        return new Response(
            $ffFactory->newCommentListHtmlPresenter()->present(
                $result,
                (int)$request->query->get( 'page', '1' )
            )
        );
    }
);
?>

Novamente, o trabalho de apresentação está embutido no código da ação.

Em geral, é melhor separar completamente o trabalho de apresentação do código da ação. Lembre-se de que em um contexto HTML, a apresentação não é somente o corpo da resposta HTML. Em vez disso, a apresentação é a resposta HTML inteira, incluindo cabeçalhos e estados (para saber mais sobre isso, veja “The Template Is Not The View”).

Com os exemplos acima, por eles já estarem bem estruturados, pode ser fácil extrair a apresentação para uma classe Responder. Por exemplo, a ação list-comments poderia ter o trabalho de apresentação completamente removido assim:

<?php
// hypothetical class with the extracted logic
class ListCommentsHtmlResponder
{
    public function buildResponse($request, $result, $ffFactory)
    {
        return new Response(
            $ffFactory->newCommentListHtmlPresenter()->present(
                $result,
                (int)$request->query->get( 'page', '1' )
            )
        );
    }
}

// the refactored action code
$app->get(
    'list-comments.html',
    function( Request $request ) use ( $app, $ffFactory ) {

        // marshal input
        $input = new CommentListingRequest(
            10,
            (int)$request->query->get( 'page', '1' )
        );

        // invoke domain and get back result
        $result = $ffFactory->newListCommentsUseCase()->listComments($input);

        // hand result to responder
        return $ffFactory->newListCommentsHtmlResponder()->buildResponse(
            $request,
            $result,
            $ffFactory
        );
    }
);
?>

Agora, o trabalho de apresentação de criar a resposta HTML está claramente separado do resto do código de ação.

III.

Ao separar as coisas nessas linhas, você começa a ver as similaridades no trabalho de apresentação, e pode começar a reduzir a repetição no código. Por exemplo, qualquer Action que entregue uma resposta JSON poderia utilizar a mesma base de Responder de JSON.

Eventualmente, você pode perceber que a lógica de cada ação é efetivamente idêntica. Isso é, você sempre coleta as entradas, encaminha essa entrada para o domínio, recebe um resultado, e passa o resultado para um construtor de respostas.

Quando essa percepção ocorrer, você pode construir um único handler pelas ações que coordena entre a organização das entradas recebidas, os pontos de entrada dos domínios e os construtores das respostas. Isso é exatamente o que o ActionHandler Arbiter faz, e o Radar utiliza isso para especificar qual Input + Domain + Reponder pode ser chamado para cada rota.

Nesse ponto, você não precisa mais escrever métodos de ação inteiramente. Então o código de interface de usuário pode focar em organizar as entradas que vão para o domínio, e em apresentar os resultados recebidos do domínio – que é exatamente como as coisas deveriam ser.

P.S.

O artigo do Jeroen também revela que ao menos alguns dos elementos de sua implementação estão retornando algo como um tipo de objeto de Domain Payload. Veja a classe ValidationResult, utilizada na ação validate-payment-data entre outros pontos.

Sou um grande fã do padrão Domain Payload no ADR, e utilizá-lo para todos os retornos recebidos do código de ação. Fazer isso simplifica a lógica de construção das respostas ainda mais; por exemplo, coletando “sucessos” e “fracassos” dos trabalhos de apresentação nas diferentes respostas em JSON.

Então, tem um pouco sobre containers:

Decidimos utilizar nossa própria ferramenta de alto nível, em vez de usar o mecanismo de injeção de dependência fornecido pelo Silex: Pimple. A nossa ferramenta, na verdade, utiliza o Pimple, apesar de não ser visto externamente. Com essa abordagem, ganhamos um melhor acesso à construção do serviço, pois podemos ter um método getLogger() com um retorno LoggerInterface tipo hint, em vez de acessar $app[‘logger’] ou algo do tipo, o que nos força a vincular a uma string e nos deixa sem o tipo hint.

Isso soa como algumas outras ideias com as quais tenho brincado, especialmente sobre o container de interface do usuário poder ser separado do container do domínio. Eles podem ser conectados separados um do outro, tornando mais fácil encapsular as partes do Domain, independentemente das partes de interface do usuário, e reforçando um limite “físico” entre elas.

No geral, parabéns ao Jeroen por escrever um artigo tão bom.

***

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/6535.

Powered by WPeMatico