Você está na página 1de 16

Frontend CifraClub

Padrões de arquitetura
VISÃO GERAL
Definição dos padrões de arquitetura usados no desenvolvimento front-end utilizando o novo
padrão de camadas.

Esse padrão é inspirado em conceitos de Clean Architecture, Dumb vs Smart components e


Flux e nesse documento será apresentado em três versões:

- Basic: Normalmente utilizadas para projetos menores, geralmente libs ou


micro-frontends que não precisem de um controle de estado global complexo;
- Redux: É uma extensão da arquitetura basic com a adição do redux, deve ser usada
para projetos que utilizam API Rest;
- Apollo: É uma extensão da arquitetura basic com a adição do apollo, ela é usada para
projetos que utilizam Graphql.

OBJETIVOS
1. Abordar quais camadas existem no projeto e suas responsabilidades;
2. Documentar convenções e padrões a serem seguidos no projeto.
CLEAN ARCHITECTURE

Como já mencionado, a Clean Architecture foi desenvolvida para outra realidade, porém,
pode ser adaptada ao front end, claro, alterando um pouco alguns conceitos para se adaptar
a essa realidade.

Common Visual Parts

Na camada mais interna da aplicação ficam os components comuns ou genéricos, que


podem ser utilizados em vários lugares. Eles não devem possuir dependências de nenhuma
regra de negócio ou especificidade que o produto possua, uma aplicação com um bom
design system vai facilitar a aplicação dessa camada.

Specific Visual Parts

Aqui ficam as views, essa camada é responsável por criar as partes visuais que aplicação
vá ter, juntando components para criar layouts compostos e com a complexidade necessária
para atender ao que o negócio necessita, devemos manter o mínimo possível de regras
aqui, idealmente apenas verificações visuais são feitos nessa camada.

Data Loading & Controllers


Na Clean Architecture original, a camada de controllers é algo como: "Um objeto que recebe
a entrada na forma que o usuário fornece e a converte na forma exigida pelo modelo".
Esse conceito é parecido com o conceito dos Containers, podemos adaptar essa descrição
para: "Um objeto que busca os dados na que a API fornece e o adapta na forma exigida
pela view".

A responsabilidade principal de um container é buscar e/ou tratar os dados que a views irá
exibir, de maneira que ela já os receba da forma correta.

Frameworks & Api Layer

Essa camada é de suma importância para o bom funcionamento da arquitetura,


centralizamos algumas coisas muito importantes aqui para garantir que o visual da
aplicação, as regras de negócio e as partes terceiras ficam concisas e sem acoplamentos
desnecessários, algumas partes importantes são:

● Modules: Auxilia nas camadas de containers e views, serve para separar o que
pode ser escrito apenas com Javascript/Typescript, do que precisa de um
Framework como React. As pages são responsáveis por carregar os dados e
tratá-los, mas ela faz isso usando funções que estarão dispostas nos modules.
● Libs: Muitas vezes precisamos criar libs ou funções que resolvem problemas
importantes e não sabemos onde exatamente elas deveriam ficar, essa camada
também suporta esse tipo de implementação.
● 3rd Party: É algo muito importante para o futuro da aplicação e para a sobrevivência
ao tempo que ela consiga se manter atualizada, é muito importante evitar de criar
dependências de terceiros dentro da nossa aplicação, então, sempre que possível
vale adicioná-las nesta camada.
● Dto & Adapters: Nem sempre o backend está sendo escrito para atender ao
frontend, uma api pode servir diversos clientes, isso faz com que os dados nem
sempre fiquem como esperamos no frontend, para evitar de criar dependências com
as propriedades que o backend tem, podemos criar adapters quando buscar algo do
backend para manter a nossa consistência de tipos e um dto quando esses dados
forem retornar para api, dessa forma garantimos que os dados estarão sempre
consistentes no front e respeitando os contratos com o backend.
BASIC - CAMADAS E CONVENÇÕES

As camadas mostradas aqui e seus conceitos são levados também para os modelos
seguintes, algumas com pequenas alterações para melhor se adaptar, mas a ideia principal
de cada camada é sempre a mesma.

1. Components
2. Containers
a. Components
3. Views
4. Modules
a. useCases
b. Actions
c. Types
d. Endpoints

1. Components

Nessa pasta ficam os componentes gerais da aplicação, normalmente um componente que


está nessa pasta deve ser auto-suficiente e possível de ser reaproveitado em qualquer
página da aplicação.

2. Containers

Um container é um componente que tem como principal objetivo centralizar as consultas e


tratamento de dados de uma página ou contexto e repassar esses dados tratados para uma
view.

Um container pode ter uma ou mais views e até mesmo um ou mais containers dentro dele,
buscando observar sempre o princípio da responsabilidade única.

Ex.:

Container de pagamento: Fazer a consulta dos dados da configuração de pagamento e


verificar se o usuário pode comprar o produto.

Ele pode ter um container dentro dele para cada método de pagamento, ou seja:

Container de boleto: Recebe via props a configuração de pagamento para boleto e trata
esses dados da maneira que for necessária para serem exibidas na view de boleto.

Container de cartão: Recebe via props a configuração de pagamento para cartão, busca os
cartões salvos do usuário, trata os dados e repassa para view de cartão.

2.a - Components

Várias vezes nossos containers ou views ficam complexos a ponto de precisar realizar a
divisão dela em componentes, ou para serem reaproveitados em mais de uma
view/container, ou para simplesmente facilitar a leitura e manutenção do código do container.
Esses componentes serão específicos do contexto que está sendo implementado, então eles
devem ser colocados dentro desse contexto apenas, a pasta de componentes do container
serve para centralizar isso e garantir a organização do código.

Ex.: Um componente de card de método de pagamento só pode ser utilizado no contexto de


pagamento, então ele ficaria nessa pasta, esse mesmo componente pode utilizar um
componente genérico de card que pode ser usado em diversos lugares da aplicação e que
ficaria na pasta de componentes genéricos (Item 1).

3. - Views

As views tem como objetivo terem o mínimo de tratamento de dados possível, elas podem
ser reaproveitadas entre containers e também podem ter views dentro delas, mas por
conceito, nunca uma view vai ter um container dentro, a view é a última etapa entre o usuário
e a aplicação.

4. - Modules

Os Modules tem como principal objetivo separar as regras de negócio dos componentes
React, e também centralizá-las de uma maneira que possam ser reaproveitadas o máximo
possível.

4.a. - UseCases

Deve centralizar os casos de uso do módulo, é muito importante manter esses


arquivos com o mínimo de dependências possíveis, dessa forma podem ser
reutilizados sempre que necessário e garantimos que as regras de negócio
sobrevivam para "sempre".

4.b. - Actions

Elas são disparadas nos containers, views e componentes, são funções que geram
alterações no estado global da aplicação ou retornam um valor atualizado para o
estado local, seja através de comunicação com a api ou com a store, ela também vai
ter funções para realizar as consultas para api.
4.c. - Types

Centralização dos tipos que serão usados nos containers, views, actions e
componentes, essa centralização é importante tanto para evitar duplicação desses
tipos, quanto para garantir a consistência dos tipos entre os diferentes lugares, além
de evitar um ciclo de dependência em alguns casos.

4.d. - Endpoints

Visando sempre a separação de tudo que for possível, além de ajudar na manutenção
a longo prazo e também a documentação de quais recursos da api estamos
consumindo, esse arquivo centraliza todas as chamadas e endpoints da api

REDUX - CAMADAS E CONVENÇÕES

1. Config
a. Store
b. AsyncReducers
2. Components
a. 3. Containers
3. Containers
a. Components
4. Views
5. Modules
a. Actions
b. Reducers
c. Enums
d. Types
e. Endpoints
f. Dto

1.a. - Store

Nesse arquivo ficam as definições de reducers estáticos ou assíncronos, sempre que um


novo reducer for adicionado a aplicação ele precisa ser adicionado na lista referente nesse
arquivo.

1.b. - AsyncReducers vs StaticReducers

Com objetivo de manter na memória do client somente os reducers necessários, criamos uma
estrutura que permite adicionar os reducers de forma assíncrona, um reducer estático vai
estar sempre disponível em qualquer página da aplicação, enquanto os reducers assíncronos
só ficam disponíveis após serem instanciados utilizando o hook withAsyncReducers

Os reducers assíncronos só são destruídos após uma nova instância no hook


withAsyncReducers, sempre que os reducers vão ser adicionados na store existe uma
comparação da lista de novos reducers a serem adicionados com a lista de reducers
assíncronos que já estão na store, essa comparação funciona da seguinte forma:

- Reducer existe na lista nova e não existe na antiga: O novo reducer será adicionado
- Reducer existe na lista nova e existe na antiga: O reducer será mantido na store
- Reducer não existe na lista nova e existe na antiga: O reducer será apagado

2. Components

Nessa pasta ficam os componentes gerais da aplicação, normalmente um componente que


está nessa pasta deve ser auto-suficiente e possível de ser reaproveitado em qualquer
página da aplicação.
3. Containers

Um container é um componente que tem como principal objetivo centralizar as consultas e


tratamento de dados de uma página ou contexto e repassar esses dados tratados para uma
view.

Um container pode ter uma ou mais views e até mesmo um ou mais containers dentro dele,
buscando observar sempre o princípio da responsabilidade única.

Ex.:

Container de pagamento: Fazer a consulta dos dados da configuração de pagamento e


verificar se o usuário pode comprar o produto.

Ele pode ter um container dentro dele para cada método de pagamento, ou seja:

Container de boleto: Recebe via props a configuração de pagamento para boleto e trata
esses dados da maneira que for necessária para serem exibidas na view de boleto.

Container de cartão: Recebe via props a configuração de pagamento para cartão, busca os
cartões salvos do usuário, trata os dados e repassa para view de cartão.

3.a - Components

Várias vezes nossos containers ou views ficam complexos a ponto de precisar realizar a
divisão dela em componentes, ou para serem reaproveitados em mais de uma
view/container, ou para simplesmente facilitar a leitura e manutenção do código do container.

Esses componentes serão específicos do contexto que está sendo implementado, então eles
devem ser colocados dentro desse contexto apenas, a pasta de componentes do container
serve para centralizar isso e garantir a organização do código.

Ex.: Um componente de card de método de pagamento só pode ser utilizado no contexto de


pagamento, então ele ficaria nessa pasta, esse mesmo componente pode utilizar um
componente genérico de card que pode ser usado em diversos lugares da aplicação e que
ficaria na pasta de componentes genéricos (Item 1).

4. - Views

As views tem como objetivo terem o mínimo de tratamento de dados possível, elas podem
ser reaproveitadas entre containers e também podem ter views dentro delas, mas por
conceito, nunca uma view vai ter um container dentro, a view é a última etapa entre o usuário
e a aplicação.

5.a. - Actions

Elas são disparadas nos containers, views e componentes, são funções que geram alterações
no estado global da aplicação, seja através de comunicação com a api ou com a store, ela
também vai ter funções para realizar as consultas para api.

Actions síncronas

É uma função que é disparada e altera o estado da store sempre que não precisar de uma
confirmação ou aguardar uma resposta da api ou de terceiros.

Ex. O usuário clicou no botão de ativar o modo escuro, essa alteração afeta uma prop na
store diretamente, o resultado dessa alteração só é monitorado pelas views/containers, então
a action só tem a responsabilidade de informar a store que essa prop mudou.

Actions Assíncronas

É uma função que é disparada e altera o estado da store só após uma confirmação ou
aguardar uma resposta da api ou de terceiros.

Normalmente pensamos nelas quando existe algum loading que será mostrado pro usuário

As actions assíncronas sempre precisam disparar 3 actions (REQUEST, FUL_FILLED,


REJECTED) e conter um try/catch.

REQUEST: Sempre que a action for chamada esse é o primeiro tipo que será disparado, ele é
responsável por ativar o estado de loading e "preparar" a store, após ela ser disparada deve
ser colocado um try/catch.

FUL_FILLED: No try fica a chamada da action de ful filled, esse é o caso de sucesso, ou seja,
se a action for na api ou num serviço de terceiro, quando esse resultado for positivo, essa
action é disparada com os dados de já preparados para serem populados na store e
removendo o estado de loading.

REJECTED: Esse é um passo muito importante, o rejected fica dentro do catch e é


extremamente importante para tratar erros que podem acontecer na api, sem ele o estado de
loading ficaria preso, é nele também que a mensagem de erro é inserida na store.

5.b. - Reducers
É ele que vai atualizar a store, é sempre imutável e só deve ser evoluído, além disso também
é importante ter um estado inicial (INITIAL_STATE) bem definido.

Os reducers são funções que recebem o estado atual, a action que foi disparada e retorna o
novo estado.

Vale lembrar que os reducers devem ser funções puras, todas as funções auxiliares que ele
utilizar devem ser funções que recebam parâmetros e retorne o valor alterado. Além disso, as
funções que são utilizadas no reducer devem estar declaradas no arquivo do próprio e
devem ser funções locais, ou seja, só serão utilizadas por esse próprio reducer.

5.c. - Enums

Os enums devem ser sempre únicos, eles são a "ligação" entre as actions e os reducers, vale
lembrar que quando uma action é disparada, todos os reducers recebem essa action, então
se os enums forem duplicados mais de um reducer será executado.

Para garantir que cada enum seja único, nós utilizamos o seguinte padrão:

[contexto] ação

ex.:

[Config Payment] Request

[Config Payment] Ful Filled

[Config Payment] Rejected

[Theme] Active Dark Mode

5.d. - Types

Centralização dos tipos que serão usados nos containers, views, actions e componentes,
essa centralização é importante tanto pra evitar duplicação desses tipos, quanto para garantir
a consistência dos tipos entre os diferentes lugares, além de evitar um ciclo de dependência
em alguns casos.

5.e. - Endpoints
Visando sempre a separação de tudo que for possível, além de ajudar na manutenção a
longo prazo e também a documentação de quais recursos da api estamos consumindo, esse
arquivo centraliza todas as chamas e endpoints da api

5.f. - Dto

Por vezes precisamos adaptar os nossos objetos no frontend para se adequar ao que a api
espera receber, para garantir a consistência do lado do frontend e a separação da
dependência com a api, esse arquivo é o único local que irá se adequar a api.

O dto idealmente vai exportar uma função dto, que recebe o objeto a ser tratado com o tipo
que o frontend utiliza e retorna esse objeto tratado e adaptado pra api.

ATENÇÃO: O dto só deve ser chamado no momento que a api for chamado, o retorno dele será
sempre direto para a api e não pode ser utilizado no frontend.

TESTES

Sempre devemos priorizar os testes unitários na aplicação, para facilitar isso, vamos dividir os
testes por camadas também:

Para testar todo o fluxo de container, view e redux precisamos dividir o teste em quatro:

- Container: Os testes devem ter os mocks das chamadas para api com consultas dos
dados, o mock da store e verificar se todas as views foram chamadas corretamente;

Caso existam chamadas de actions essas devem ser mockadas e testadas de maneira
que garanta que a action foi chamada com os parâmetros esperados e mockado o
seu retorno, caso este exista.

- View: Nos testes da view os dados devem vir através de props, validando o
comportamento que ela deve ter baseado nessas props;

Caso existam chamadas de actions essas devem ser mockadas e testadas de maneira
que garanta que a action foi chamada com os parâmetros esperados e mockado o
seu retorno, caso este exista.

- Actions: Os testes de actions devem ter os mocks para api e verificar se as actions
estão sendo disparadas da maneira que o redux espera.
- Reducer: Dado um estado inicial e uma action disparada com seu payload é esperado
um resultado final no reducer.
APOLLO - CAMADAS E CONVENÇÕES

1. Config
a. apollo
2. Components
3. Containers
a. Components
4. Views
5. Modules
a. Actions
b. Types
c. Dto
d. Queries
e. Vars

1.a. Apollo

Nesse arquivo é onde fica a configuração do ApolloClient, exportando a função de


apolloClient que será usada para disparar o apollo na aplicação.

Deve ser criado nele o cache e o httpLink para a api.


2. Components

Nessa pasta ficam os componentes gerais da aplicação, normalmente um componente que


está nessa pasta deve ser auto-suficiente e possível de ser reaproveitado em qualquer
página da aplicação.

3. Containers

Um container é um componente que tem como principal objetivo centralizar as consultas e


tratamento de dados de uma página ou contexto e repassar esses dados tratados para uma
view.

Um container pode ter uma ou mais views e até mesmo um ou mais containers dentro dele,
buscando observar sempre o princípio da responsabilidade única.

Ex.:

Container de pagamento: Fazer a consulta dos dados da configuração de pagamento e


verificar se o usuário pode comprar o produto.

Ele pode ter um container dentro dele para cada método de pagamento, ou seja:

Container de boleto: Recebe via props a configuração de pagamento para boleto e trata
esses dados da maneira que for necessária para serem exibidas na view de boleto.

Container de cartão: Recebe via props a configuração de pagamento para cartão, busca os
cartões salvos do usuário, trata os dados e repassa para view de cartão.

3.a - Components

Várias vezes nossos containers ou views ficam complexos a ponto de precisar realizar a
divisão dela em componentes, ou para serem reaproveitados em mais de uma
view/container, ou para simplesmente facilitar a leitura e manutenção do código do container.

Esses componentes serão específicos do contexto que está sendo implementado, então eles
devem ser colocados dentro desse contexto apenas, a pasta de componentes do container
serve para centralizar isso e garantir a organização do código.

Ex.: Um componente de card de método de pagamento só pode ser utilizado no contexto de


pagamento, então ele ficaria nessa pasta, esse mesmo componente pode utilizar um
componente genérico de card que pode ser usado em diversos lugares da aplicação e que
ficaria na pasta de componentes genéricos (Item 1).

4. - Views

As views tem como objetivo terem o mínimo de tratamento de dados possível, elas podem
ser reaproveitadas entre containers e também podem ter views dentro delas, mas por
conceito, nunca uma view vai ter um container dentro, a view é a última etapa entre o usuário
e a aplicação.

5.a. Actions

Elas são disparadas nos containers, views e componentes, são funções que geram alterações
no estado global da aplicação através das ReactiveVars, nela será importado o apolloClient
da config e dispara as queries.

5.b. - Types

Centralização dos tipos que serão usados nos containers, views, actions e componentes,
essa centralização é importante tanto pra evitar duplicação desses tipos, quanto para garantir
a consistência dos tipos entre os diferentes lugares, além de evitar um ciclo de dependência
em alguns casos.

5.c. - Dto

Por vezes precisamos adaptar os nossos objetos no frontend para se adequar ao que a api
espera receber, para garantir a consistência do lado do frontend e a separação da
dependência com a api, esse arquivo é o único local que irá se adequar a api.

O dto idealmente vai exportar uma função dto, que recebe o objeto a ser tratado com o tipo
que o frontend utiliza e retorna esse objeto tratado e adaptado pra api.

ATENÇÃO: O dto só deve ser chamado no momento que a api for chamado, o retorno dele será
sempre direto para a api e não pode ser utilizado no frontend.
5.d. Queries

Esse diretório fica responsável por centralizar todas as queries da aplicação, criando os
arquivos com a extensão .graphql no seguinte padrão [nome_da_query].graphql.

5.e. Vars

Para utilizar o apollo para controlar o estado global da aplicação usamos as duas funções
do apolloClient: makeVar e ReactiveVar.

Nesse arquivo nós criamos a variável que será usada como estado e a exportamos como
uma ReactiveVar do tipo que precisamos, exemplo:

export const citiesVar: ReactiveVar<City> = makeVar({


name: "",
loaded: false
});

Você também pode gostar