[ptBR] Arquitetura - leonardonegrao/arch-studio GitHub Wiki
Arquitetura da aplicação
Durante o desenvolvimento de uma aplicação front-end, arquitetura é uma importante peça, e muitas vezes ignorada. Isso se torna mais verdadeiro quando se fala em aplicações React.js.
Não é produtivo entrar na discussão sobre React ser ou não um framework, mas de fato, em fatores de estruturação de aplicações, o React tende mais ao lado de ser apenas uma biblioteca.
Angular é um exemplo oposto. Aplicações em Angular possuem estruturas sólidas baseadas na filosofia do próprio framework, isso, apesar de por muitos ser considerado "limitante" ou "trabalhoso" (até porque mudança de contexto constante é trabalhosa sim), traz o benefício da garantia de boas práticas serem impostas.
Desde o começo, o React teve problemas em convencer a comunidade de que seu código é organizado. E, de fato, se utilizado por si só, pode ser desorganizado (inclusive sendo possível fazer uma aplicação inteira em um único componente como o Kent C. Dodds fala).
O Next.js de certa forma melhora tudo isso. Estruturas mais bem definidas são consequência do uso desse framework. Ao mesmo tempo, outro ponto surge: quanto da aplicação pode ser dependente do framework em si? A implementação do framework deveria afetar a maioria dos componentes?
Enfim, todos esses pontos foram pensados ao desenvolver a aplicação.
O src, TypeScript e a visão geral
O src é o ponto principal da aplicação. No Next.js é possível implementar a pasta pages e a pasta components separadamente, na raiz, mas a escolha aqui foi de colocá-las dentro de src e separar o que é a aplicação em si do que é configuração e outras pastas (que raramente são manipuladas).
No src há as seguintes pastas:
components, onde se encontram os componentes da aplicação;infra, que contém código relacionado à infraestrutura, comohookspersonalizados e acessos a APIs (no caso, ao CMS);models, que carrega modelos (ou entidades) gerais, que acabam sendo reutilizadas em vários pontos da aplicação;pages, uma pasta que o próprio Next.js exige, que contém qualquer página da aplicação;theme, em que estão todas as configurações relacionadas a tema, Design System e ao estilo global.
Assim, a aplicação já possui uma boa separação de responsabilidades que facilita o desenvolvimento (principalmente em relação ao escopo de desenvolvimento, que vai ser mais comentado na seção sobre components).
Um ponto que não necessariamente influenciou em decisões de arquitetura, mas reflete essas decisões, são os caminhos absolutos do TypeScript. Em tsconfig.json, foram configurados os seguintes caminhos:
@components/@pages/@theme/@public/@models/@infra/
Isso significa que, ao fazer importações, os caminhos não precisam ser relativos, e podem seguir os "codinomes" das pastas. Assim, mudar o local de arquivos que importam outros códigos não é tão complicado, porque não há tanta noção sobre o caminho que é necessário para acessar o outro arquivo.
Isso é comum em outras linguagens e frameworks, como no Flutter.
Essa primeira camada, junto do TypeScript, é o primeiro nível de proteção de independência entre arquivos.
Pasta components e a entropia
A pasta components é composta por todos os componentes da aplicação. Esses componentes são divididos em quatro tipos:
- Em
commonestão componentes reutilizados por toda a aplicação, que são atômicos e independentes do contexto, comoButtoneInput; - Em
foundation, são componentes fundamentais da aplicação, similares atokens, como oTypography; - Em
screenssão os componentes principais das telas. Como o Next.js exige a existência de arquivos empagespara lidar tanto com as rotas quandogetStaticProps, a primeira camada da página foi separada dentro desses componentes dentro descreens. Mais sobre na própria seção; wrapperssão componentes reutilizáveis que implementam tudo que é comum entre componentes, é por meio dowrapperque o componente é renderizado, já dentro de um contexto básico;
É nesse ponto que começa a lógica de escopo de desenvolvimento. Tendo em vista a independência entre componentes, o desenvolvimento tende a seguir um escopo de modificações.
Assim, componentes primários são distribuídos entre as pastas, como common e foundation, mas componentes que estão limitados a outros componentes estão aninhados.
Por exemplo, como uma screen é a composição de vários componentes que resulta em uma página, todos esses componentes estão dentro da pasta da screen, em uma pasta components.
O resultado é uma entropia menor nas dependências de um componente. Dessa forma, um componente sempre, ou acessa um componente global, ou acessa componentes-filhos.

O uso das screens faz parte desse processo também. Tudo que se depende acaba mais "próximo", ao invés de um arquivo de pages importar vários componentes de uma pasta distante, o que dificultaria tanto o entendimento de que um componente faz parte de certa página, como a estrutura das pastas em components seria pouco intuitiva.
Esses conceitos foram fortemente baseados na palestra da Monica Lent, na GOTO 2019, entitulada Building Resilient Frontend Architecture, em que a palestrante discute a diferença entre desacoplamento e "DRY" (Don't Repeat Yourself), e que, o desacoplamento pode ser mais benéfico para uma aplicação do que o DRY.

Pasta screens, wrappers componentes Link e Image e desacoplamento do framework
Apesar dos benefícios de se utilizar um framework, quanto mais se aproveita das vantagens, mais cria-se uma dependência entre a aplicação e o framework. A solução para isso é garantir que comportamentos referentes ao framework estejam encapsulados e sendo acessados a partir de um único ponto.
Além disso, como em uma aplicação, componentes genéricos providos pelo framework podem acabar necessitando de múltiplas configurações, o melhor a se fazer é evitar que outros componentes sejam responsáveis por configurar algo tão específico.
O Next.js logo nas páginas já possui esse tipo de "influência". E logo nas páginas, há inevitáveis configurações repetitivas:
- Componentes como
HeadereFooter, que são parte de todas as páginas; - Configurações de SEO, que precisam ser feitas em todas as páginas;
- Estilo global e provedor de temas do Styled Components, os quais a aplicação inteira usa.
Sem uma solução eficaz, as importações seriam repetidas, a manutenção de qualquer parte poderia ser dificultada e o resultado seria o mesmo. E com isso, os wrappers aparecem.
A aplicação possui apenas um wrapper, que é o de páginas, o PageComponentWrapper.
O PageComponentWrapper possui várias configurações, incluindo as explicadas acima, como também algumas definições de estilo. Esse componente também e divido em algumas partes:
- O
pageComponentHocsegue a ideia de Higher Order Components. Essa ideia vem das HOFs (Higher Order Functions) da programação funcional, que é possível no JavaScript pela possibilidade de funções receberem ou retornarem outras funções. Para o contexto do React.js e do HOC, isso significa que componentes podem receber/retornar outros componentes. Como uma fábrica, esse componente recebe o componente dascreenque deve ser renderizado e o retorna com todos os outros detalhes necessários; - O
GlobalProvideré o provedor global da aplicação, e é o local que contextos (da Context API) serão carregados. Nesta aplicação, apenas oThemeProviderdo Styled Components foi necessário.

No caso do Link e do Image, a ideia é similar. Usando diretamente os componentes oferecidos pelo Next.js (next/image e next/link), a aplicação dependeria da implementação definida pelo Next.js.
O lado negativo disso é mais claro no componente Image, feito pela equipe do Next.js para ser um componente flexível, que pode ser usado de diferentes maneiras e, consequentemente, espera muitas instruções de como ser criado.
No entanto, na aplicação, todas as vezes, a imagem precisava ser renderizada com um tamanho responsivo do seu container - o que por si só já exige um container. Isso implica em mais configurações, como objectFit e layout.
Tudo foi encapsulado em um componente próprio chamado Image, que recebe configurações da sua própria forma.
O caso do Link é similar, apesar de menos problemático.
O que é um componente
Apesar de componentização ser um tema popular e bem definido, a divisão de componentes não é bem definida. Decidir o que é um componente não é necessariamente simples ou óbvio.
Aqui, componentes são trechos de código independentes. Por ser uma aplicação mais visual do que comportamental, grande parte do código é HTML e Styled Components, e assim, a componentização é mais flexível.
Todo trecho que é independente, e que polui consideravelmente o retorno de um componente (e por consequência afeta a experiência de desenvolvimento com distrações) foi componentizado. Isso significa que seções de páginas foram componentizadas, pois seus conteúdos são independentes entre si, mesmo que comportamentalmente elas não tornaram o gerenciamento do componente-pai muito complexo.
A componentização, aliada aos componentes aninhados, permite que a estrutura da aplicação replique a árvore de componentes sendo renderizada, tornando a navegação pelo código mais intuitiva.
A consequência: Prop Drilling
Prop Drilling é o nome dado ao cenário de passar uma prop por vários componentes encadeados por ela estar definida vários nós acima de onde precisa ser usada na árvore de componentes.
O assunto é extensamente discutido, e várias soluções surgiram com o passar do tempo, como Redux e a própria Context API.
Com a estrutura dessa aplicação, há casos de props criadas dentro da page, sendo passadas para screens, que possuem suas próprias seções componentizadas e finalmente recebem as props.
Pode parecer um problema, mas é uma solução eficaz de passar estado. O estado no componente-pai, sendo distribuído para os componentes-filhos é chamado de Lifted State e a necessidade de manter o getStaticProps junto das pages incentivou essa metodologia, que acabou fazendo mais sentido do que usar Context API.
