Implementando Clean Architecture no ReactJS

Como deixar sua aplicação menos dependente da biblioteca

João Guilherme Berti Sczip
7 min readJul 23, 2020
Robert C. Martin Clean Architecture

Com as ferramentas atuais de desenvolvimento de software presentes no mercado e a constante adoção de diversos frameworks e bibliotecas, está cada vez mais fácil encontrar desenvolvedores que se deixam levar pela praticidade e produtividade que chegam aliadas a essas ferramentas. Entretanto, é preciso tomar cuidado, visto que com a constante evolução presente nessa área, não são raros os casos em que essas mesmas ferramentas tornam-se obsoletas, fazendo com que as empresas precisem manter softwares com código legado ou migrarem para as tecnologias mais recentes. O problema aqui não é o uso das ferramentas, elas devem sim ser utilizadas, mas com ressalvas, não queremos nossos produtos dependentes de um framework, mas sim que esses mesmos frameworks se adéquem aos nossos produtos. Tendo isso em mente, alguns estudos e metodologias surgiram com a tarefa de tentar diminuir esses problemas, uma delas é a Clean Architecture, apresentada por Robert C. Martin.

Esse artigo é o primeiro da série, aqui está o link para a parte 2.

Clean Architecture

As dependências de código-fonte devem apontar apenas para dentro, na direção das políticas de nível mais alto. Robert C. Martin.

A ideia principal por trás da Clean Architecture é a Regra da Dependência, que consiste basicamente em dizer para onde as dependências da nossa arquitetura devem apontar, no caso, sempre apontando para as políticas de mais alto nível.

O que são as políticas de mais alto nível? Martin as define como os componentes que só sofrerão alterações caso as regras de negócios sejam alteradas, como os casos de uso e as entidades, ou seja, apenas em casos muito específicos.

Já as políticas de baixo nível são onde realizamos implementações de bibliotecas e frameworks de terceiros, além de comunicações com o mundo externo.

Qual a vantagem disso? A principal vantagem é manter o núcleo da aplicação independente da tecnologia que estamos utilizando para desenvolver o produto, tornando o código muito mais flexível, reutilizável, abstraído, de fácil manutenção e evolução. Uma vez que os nossos casos de uso não estão atrelados a nenhuma tecnologia em si, a troca de classes concretas, caso necessário, pode ser realizada de forma bastante fácil, desde que possuam interfaces bem definidas.

Clean Architecture no ReactJS

O projeto a ser desenvolvido será um sistema de reconhecimento de imagens, que contará com 2 casos de uso principais, que são:

  • Enviar a imagem ao serviço responsável pelo reconhecimento (no exemplo será utilizado uma API própria);
  • Salvar o resultado da detecção no cache (exemplo puramente didático, visto não haver necessidade).

Escolhi esse projeto e esses 2 casos de uso para exemplificar duas implementações distintas, remota e local, de forma com que suas classes concretas serão bem diferentes. A Figura 1 ilustra o diagrama arquitetural definido para ambos casos de uso:

Figura 1 — Diagrama das camadas da aplicação

Observando a imagem, é possível destacar alguns pontos:

  • Divisão em camadas;
  • Regra da dependência (flechas apontando sempre para as políticas de mais alto nível);
  • Utilização de interfaces;

Essa diagrama exemplifica de forma clara qual o objetivo em adotarmos uma arquitetura desse tipo, como definimos as camadas, quais interfaces serão utilizadas, quais serão as dependências, etc.

Das camadas da aplicação

Domain Layer: A camada mais acima, responsável por armazenar os casos de uso e as entidades, nela estarão presentes todos os protocolos definidos pelo domínio, é a política de nível mais alto presente na aplicação, não há implementações concretas nessa camada, apenas interfaces.

Os casos de uso foram definidos como as interfaces SendImage (responsável pelo envio da imagem ao servidor) e StoreResults (responsável por armazenar os resultados em cache), logo abaixo observamos suas implementações concretas, RemoteSendImage e LocalStoreResults, respectivamente. Obs: No diagrama as implementações para ambas interfaces estão juntas no Domain Layer, entretanto, elas são implementadas no Data Layer.

Data Layer: A camada de dados é responsável por implementar as classes concretas dos protocolos definidos em Domain Layer. Pode-se observar que não há comunicação direta com o domínio, as comunicações se dão apenas através das interfaces, tornando o código mais flexível para mudanças, mantendo um acoplamento mais baixo entre as duas camadas. Aqui também serão definidos os protocolos que serão implementados na camada de infraestrutura, que está logo abaixo.

Infra Layer: A camada de infraestrutura é responsável por realizar a comunicação com serviços externos, através de bibliotecas de terceiros ou não, em nosso exemplo teremos duas classes concretas nessa camada, o AxiosHttpClient (responsável por realizar requisições http) e o LocalStorageAdapter (responsável por salvar dados no localStorage do browser).

É importante sempre destacar a direção em que as flechas estão apontando, no diagrama, observamos que RemoteSendImage só depende de SendImage, seu protocolo, que por sua vez não depende de ninguém. Esse é o objetivo em em fazer uso da Clean Architecture, dessa forma impedimos que qualquer alteração no AxiosHttpClient, como uma mudança de implementação por exemplo, não cause nenhum problema nos casos de uso.

Agora que já temos a definição e o objetivo por trás da Clean Architecture, além da arquitetura definida para o projeto, vamos ao código.

Criação do projeto e estrutura de pastas

Para fins didáticos, utilizei o Create React App (CRA) para a criação da estrutura do projeto com Typescript (há casos em que o mais interessante é configurar o projeto manualmente, para não ficar preso à estrutura definida pelo CRA). Após o projeto criado, organizei a estrutura de pastas de acordo com os camadas definidas no diagrama, a nova estrutura pode ser conferida na Figura 2.

Figura 2 — Estrutura de pastas

Ainda não expliquei sobre as camadas main e presentation, pretendo explicá-las posteriormente, quando for hora de implementá-las, por ora vamos nos ater apenas as 3 que foram ilustradas no diagrama: Domain, Data e Infra.

SendImage Use Case

Agora que o projeto está configurado e a estrutura definida, vamos começar o desenvolvimento definindo as interfaces dos casos de uso, iniciando pelo SendImage.

Dentro de domain \ usecases, criei o arquivo send-image.ts.

Figura 3 — SendImage use case

SendImage é apenas uma interface com um método send, logo abaixo defini um namespace e um type alias para os parâmetros que o método irá receber. O retorno da função será uma entidade do tipo DetectedImage.

Figura 4 — DetectedImage model

DetectedImage é apenas um tipo, definido em domain \ models, é a representação do retorno do método send.

Agora que já temos o caso uso pronto, podemos partir para a sua implementação. Entretanto, conforme o diagrama apresenta, para que RemoteSendImage funcione, é necessário injetar uma dependência do tipo HttpPostClient, então, dentro de data \ protocols \ http, crio o arquivo http-post-client.ts

Figura 5 — HttpPostClient

Assim como nosso caso de uso, HttpPostClient é também apenas uma interface, um protocolo que futuramente será implementado por uma classe concreta responsável por efetuar as requisições http.

Agora com a dependência pronta, podemos finalizar a implementação de nosso caso de uso:

Figura 5 — RemoteSendImage

Alguns pontos importantes para destacar em nossa implementação:

  • Os atributos da classe recebidos através do construtor são específicos apenas para esta implementação, tanto a url como o httpPostClient são necessários para efetuarmos requisições http ao servidor. Ex: caso a implementação utilizasse algum outro protocolo, provavelmente as dependências da classe seriam outras.
  • RemoteSendImage não tem ideia de como fazer uma requisição http, com isso, delega essa responsabilidade à qualquer implementação de HttpPostClient, não se importando se é uma requisição através do axios, fetch ou qualquer outra ferramenta.
  • Observe que pegamos apenas os dados necessários do corpo da resposta, não nos preocupando com o resto, nosso caso de uso não está interessado em saber de que forma esses dados foram recebidos, só quer receber do formato correto.

Para o artigo não ficar muito grande e cansativo, irei separá-lo em mais partes, assim que tiver com as outras prontas colocarei os links aqui.

Conclusão

Ainda que através do exemplo prático que iniciei não tenha sido possível observar a real importância do Clean Architecture, busquei exemplificar da forma mais fácil e simples possível, para que ao final do projeto tudo fique mais claro, além de dar um norte para quem não tem ideia do que seja e tenha interesse em ir atrás do assunto.

Quem trabalha com desenvolvimento de software sabe que não existe uma bala de prata, não há uma ferramenta ou metodologia que sirva para todos os nossos problemas, portanto, cabe a nós mesmos termos o conhecimento para decidirmos o que e quando utilizar. O assunto que abordei aqui é um dos que considero mais importante para a área de software, é muito fácil escrever código, qualquer um com um mínimo conhecimento necessário consegue construir algumas coisas, porém, criar uma aplicação que seja fácil de evoluir, manter, que possua suas fronteiras bem definidas, que o acoplamento entre os componentes seja baixo, fazer com que a arquitetura grite qual o objetivo desse software, não são todos que tem essa capacidade. Os estudos e metodologias estão aí para serem lidos e adequados a nossa realidade, cada implementação vai de caso em caso.

Links e referências

--

--