Arquitetura Limpa: Aplicando com React
Tags: react, typescript, cleanarchitecture, braziliandevs
Published: 2022-07-15 17:18:00

Esse texto faz parte de uma série de textos sobre análise da [Arquitetura Limpa](https://dev.to/rubemfsv/arquitetura-limpa-o-conceito-por-tras-do-codigo-4kdf) aplicada com frameworks e linguagens distintas.

Os propósitos deste texto seguem alinhados com os do texto anterior, sendo eles: I. Mostrar uma divisão arquitetural de uma aplicação React usando Arquitetura Limpa; II. Guiar a implementação de novas features nesta arquitetura proposta.

## Divisão Arquitetural

O passo inicial é analisar como é feita a divisão.

```
cypress/
src/
  data/
    protocols/
    test/
    usecases/
  domain/
    errors/
    models/
    test/
    usecases/
  infra/
    cache/
    http/
    test/
  main/
    adapters/
    config/
    decorators/
    factories/
      cache/
      decorators/
      http/
      pages/
      usecases/
    routes/
    scripts/
    index.tsx
  presentation/
    assets/
    components/
    hooks/
    pages/
    protocols/
    routes/
    styles/
    test/
  requirements/
  validation/
    errors/
    protocols/
    test/
    validators/
```

Em detalhes, o propósito de cada estrutura de arquivos é o seguinte:

- **cypress**: Contém os arquivos de teste end to end da aplicação (para projetos grandes, essa pasta é recomendável que seja em um projeto a parte, para que o time responsável pelos testes e2e cuide dele, uma vez que não precisa conhecer o código do projeto).
- **src**: Contém todos os arquivos necessários para o sistema.
 - **Data**: A pasta data representa a camada de dados da Arquitetura Limpa, sendo dependente da camada de domínio. Contém as implementações das regras de negócio que são declaradas no _domain_.
 - **Domain**: Representa a camada de domínio da Arquitetura Limpa, a camada mais interna da aplicação, não apresentando dependência com nenhuma outra camada, onde contém as regras de negócio.
 - **Infra**: Essa pasta contém as implementações referentes ao protocolo HTTP e ao cache, também é único local onde terá acesso a dependências externas relacionadas para esses dois itens citados. Aqui também é onde está contido a maioria das bibliotecas externas.
 - **Main**: Corresponde a camada principal da aplicação, ponto que ocorre a integração das interfaces desenvolvidas na camada de UI, com as regras de negócio criadas nas pastas que representam as camadas mais internas da Arquitetura Limpa. Tudo isso se dá devido ao uso de padrões de projetos como Factory Method, Composite e Builder.
 - **Presentation**: Nesta pasta contém a parte visual da aplicação, com suas páginas, componentes, _hooks_, assets e estilizações.
- **Requirements**: Contém os requisitos do sistema documentados.
- **Validation**: Onde contém as implementações das validações utilizadas nos campos.

Diferente da [abordagem com Flutter](https://dev.to/rubemfsv/arquitetura-limpa-uso-com-flutter-2i68) onde havia uma pasta central que se concentravam todos os testes, nessa abordagem os testes se encontram nas respectivas pastas dentro da `src`.

## Guia de Implementação

Nesta seção será descrita uma sequência lógica recomendada para um melhor desempenho de implementação de sistemas React utilizando esta arquitetura. 

Para finalidade de simplificar a explicação, não será descrito em detalhes os testes unitários. No entanto, é fortemente recomendado começar pelos testes unitários antes do desenvolvimento (TDD) de cada passo utilizando os _requirements_ para embasar os cenários. E após finalizar os cenários, fazer o teste end to end do fluxo (se for um dos principais, ter em mente a [pirâmide de testes](https://martinfowler.com/articles/practical-test-pyramid.html)).

A demonstração a seguir é da criação do fluxo de Login para entrar em uma aplicação.

### Primeiro passo: Criar as regras de negócio na camada de domínio

Dentro de src/domain/usecases, criar o authentication.ts. Esse arquivo será uma interface que vai descrever a regra de negócio da autenticação.

```
import { AccountModel } from '@/domain/models/';

export interface IAuthentication {
  auth(params: Authentication.Params): Promise<Authentication.Model>;
}

export namespace Authentication {
  export type Params = {
    email: string;
    password: string;
  };

  export type Model = AccountModel;
}
```

Como vemos, essa interface tem um método `auth()` que recebe os parâmetros _Authentication.Params_ que são declarados num namespace abaixo - contendo o tipo dos parâmetros (email e password) e o tipo do model (_AccountModel_) - e espera retornar um _Authentication.Model_ de maneira assíncrona. 

O _AccountModel_ é uma exportação nomeada do model criado em src/domain/models que representa o token que é retornado após a autenticação para persistir a sessão.

```
export type AccountModel = {
  accessToken: string;
};
```

### Segundo passo: Implementar as regras na camada de dados

Nessa camada, criamos o caso de uso para implementar a interface criada anteriormente na camada de domínio, porém dentro de src/data/usecases.

O arquivo tende a ficar como o exemplo abaixo.

```
import { IHttpClient, HttpStatusCode } from '@/data/protocols/http';
import { UnexpectedError, InvalidCredentialsError } from '@/domain/errors';
import { IAuthentication, Authentication } from '@/domain/usecases';

export class RemoteAuthentication implements IAuthentication {
  constructor(
    private readonly url: string,
    private readonly httpClient: IHttpClient<RemoteAuthenticationamespace.Model>
  ) {}

  async auth(
    params: Authentication.Params
  ): Promise<RemoteAuthenticationamespace.Model> {
    const httpResponse = await this.httpClient.request({
      url: this.url,
      method: 'post',
      body: params,
    });

    switch (httpResponse.statusCode) {
      case HttpStatusCode.ok:
        return httpResponse.body;
      case HttpStatusCode.unauthorized:
        throw new InvalidCredentialsError();
      default:
        throw new UnexpectedError();
    }
  }
}

export namespace RemoteAuthenticationamespace {
  export type Model = Authentication.Model;
}
```

Como podemos observar, a classe _RemoteAuthentication_ implementa a interface _IAuthentication_, recebendo o cliente HTTP e a url para a requisição. No método `auth()` ele recebe os parâmetros, e chama o httpClient passando a url, o método (nesse caso é o _post_) e o body (que são os parâmetros). Esse retorno é uma _httpResponse_ do tipo referente ao _Authentication.Model_ que tem um código de status de resposta, e que a depender do seu resultado, dá o respectivo retorno - podendo retornar o valor esperado pela requisição ou um erro. 

Os códigos de status são os [HTTP](https://developer.mozilla.org/pt-BR/docs/Web/HTTP/Status):

```
export enum HttpStatusCode {
  ok = 200,
  created = 201,
  noContent = 204,
  badRequest = 400,
  unauthorized = 401,
  forbidden = 403,
  notFound = 404,
  serverError = 500,
}
```

### Terceiro passo: Implementar as páginas na camada de _presentation_

Para simplificar o entendimento, será apresentado apenas trechos de códigos referentes a chamada do método de autenticação. A página de Login contém mais ações e detalhes que vão além da autenticação. Levar em consideração o protótipo da página abaixo para facilitar a visualização.

![Login page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sequhhg8vfgq0hbjylb2.jpeg)

Em src/presentation/pages/ será criada a página de Login que é composta por componentes, métodos e funções. O componente que chama o método de autenticação é o `<Button/>` que está contido no formulário para pegar os valores dos inputs, conforme o trecho de código a seguir:

```
<form
  data-testid="loginForm"
  className={Styles.form}
  onSubmit={handleSubmit}
> 
  <Input
    autoComplete="off"
    title="Digite seu e-mail"
    type="email"
    name="email"
  />
  <Input
    autoComplete="off"
    title="Digite sua senha"
    type="password"
    name="password"
    minLength={6}
  />
  <Button
    className={Styles.loginBtn}
    type="submit"
    disabled={state.isFormInvalid}
    title="Entrar"
    data-testid="loginButton"
  />
</form>
``` 
Ao clicar no `Button`, é chamado o `handleSubmit()` que está no `onSubmit` do `form`. 

```
const handleSubmit = async (
    event: React.FormEvent<HTMLFormElement>
  ): Promise<void> => {
    event.preventDefault();
    try {
      const account = await authentication.auth({
        email: state.email,
        password: state.password,
      });

      setCurrentAccount(account);
      history.replace('/');
    } catch (error) {
      // Error handling here
    }
  };
```

Onde o `authentication.auth()` clicado chamará uma _factory_ (veremos mais a seguir) para fazer a autenticação. Nesse caso está passando os parâmetros capturados pelo input e o valor retornado da requisição é salvo no cache através do `setCurrentAccount(account);`.

### Quarto passo: Conectar todas as camadas para que as requisições funcionem

Após tudo implementado, agora basta conectar todas as partes. Para isso, é utilizado o padrão de projeto [Factory Method](https://refactoring.guru/pt-br/design-patterns/factory-method).

Dentro de src/main/factories/usecases, criamos a _factory_ do caso de uso que está sendo implementado. No caso desse exemplo, é o relacionado a autenticação. 

É criado o _makeRemoteAuthentication_, que retorna o _RemoteAuthentication_ que recebe como parâmetro a _factory_ que cria a URL e a _factory_ do _Http Client_. É passado como parâmetro a URL da API que deseja requisitar junto da _factory_ que cria a URL. No exemplo é a URL que finaliza com _/login_.

```
import { RemoteAuthentication } from '@/data/usecases/';
import { IAuthentication } from '@/domain/usecases';
import { makeAxiosHttpClient, makeApiUrl } from '@/main/factories/http';

export const makeRemoteAuthentication = (): IAuthentication => {
  const remoteAuthentication = new RemoteAuthentication(
    makeApiUrl('/login'),
    makeAxiosHttpClient()
  );

  return remoteAuthentication;
};
```

Após isso, em src/main/factories/pages, é criada a pasta para as factories do Login. Em páginas com formulário também são injetadas validações, porém como o foco desse texto são as integrações, deixaremos esse ponto de fora da explicação.

```
import React from 'react';
import { Login } from '@/presentation/pages';
import { makeRemoteAuthentication } from '@/main/factories/usecases/';

const makeLogin: React.FC = () => {
  const remoteAuthentication = makeRemoteAuthentication();

  return (
    <Login
      authentication={remoteAuthentication}
    />
  );
};

export default makeLogin;
```

É criada uma const `makeLogin` que representa a _factory_. Ela possui o `makeRemoteAuthentication` que é injetado dentro da página de Login criada na camada de _presentation_ para que a página tenha acesso a essas requisições.

### Quinto passo: Aplicar a página criada na aplicação

Por fim, é necessário adicionar a _factory_ do Login nas rotas da aplicação para que ela seja acessada pelos usuários. 

No arquivo _router.tsx_ que fica localizado em src/main/routes, adicionar a _factory_ da página criada dentro do _Switch_ do _BrowserRouter_. É passado no _path_ a rota, no caso é a _/login_, e a página no _component_, que no caso é o ponteiro para a _factory_ `makeLoginPage`. Essa lógica é utilizada com todas as outras páginas, alterando apenas de Route para PrivateRoute caso a rota seja autenticada. O código parecido com este abaixo.

```
const Router: React.FC = () => {
  return (
    <ApiContext.Provider
      value={{
        setCurrentAccount: setCurrentAccountAdapter,
        getCurrentAccount: getCurrentAccountAdapter,
      }}
    >
      <BrowserRouter>
        <Switch>
          <Route exact path="/login" component={makeLogin} />
          <PrivateRoute exact path="/" component={makeDashboard} />
        </Switch>
      </BrowserRouter>
    </ApiContext.Provider>
  );
};
```

## Conclusão

A Arquitetura Limpa apesar de ser um pouco complexa de se entender e implementar no começo - e até parecer redundante -, as abstrações são necessárias. São aplicados diversos padrões de projetos para garantir a qualidade e independência do código, facilitando a evolução e manutenção independente de framework. Em casos como esse, se desejar mudar o framework de _React_ para _Angular_ ou qualquer outro baseado em _Typescript_, basta mudar a camada de apresentação e fazer ajustes nas dependências.

Seguir o processo de desenvolvimento e entender o porquê está fazendo de tal maneira facilita a produção do código. Após um tempo acaba sendo feito de maneira natural, pois tem um processo linear de desenvolvimento: I. Caso de uso na camada de domínio; II. Caso de uso na camada de dados; III. Criação das interfaces na camada de presentation; IV. Criação das _factories_ para integrar todas as camadas na camada principal; V. E a chamada da _factory_ principal nas rotas da aplicação. 

Pelo exemplo ter muitas partes abstraídas, é recomendável a leitura do código das partes ocultas para uma maior compreensão. [Nesse repositório](https://github.com/rubemfsv/clean-react-app) você consegue ter acesso a códigos abstraídos similares ao desse exemplo dado. 

Você também pode ter acesso a essa arquitetura rodando apenas o comando `npx @rubemfsv/clean-react-app my app`, similar ao create-react-app, mas de uma maneira mais limpa e escalável. 

## Referências

* Rodrigo Manguinho https://github.com/rmanguinho/clean-react
* MARTIN, Robert C. Clean Architecture: A Craftsman’s Guide to Software Structure and Design. 1st. ed. USA: Prentice Hall Press, 2017. ISBN 0134494164.