Cebolas e camadas para padrões de projetos no Front-end — Parte I
Tags: frontend, architecture, react, javascript
Published: 2024-06-19 12:35:40

Nesse texto, tenho como objetivo trazer uma alternativa de padrões de projetos front-ends, esse padrão funciona independente do framework ou biblioteca.

A estrutura proposta neste artigo utiliza alguns recursos e nomenclaturas conhecidas pela comunidade do ReactJS, mas outras são uma inspiração de outros ecossistemas como Angular e similares, a ideia é compartilhar um padrão que já adotei em alguns projetos e que serve muito bem para um monolito escalável, e com uma ótima fórmula para modelar um ecossistema que poderá evoluir para um micro-frontend em um futuro próximo.

# O que vamos encontrar aqui?

- Um projeto To-Do List com ReactJs e arquitetura onion.
- Referências a outras publicações que irá descrever melhor alguns contextos e implementações.
- Uma simples comparação entre uma arquitetura layered e uma onion.
- Um relato dos projetos em que implementei essa composição com pontos positivos e negativos.

# Mas antes de começar:

- O projeto de exemplo não possui uma API para gerenciar acessos e salvar dados, ou seja, os dados são salvos localmente no navegador.
- Essa arquitetura não é uma bala de prata, é complexa e robusta para escalar grandes projetos, para algo menor considere recursos mais objetivos como um MVC por exemplo.
- Os nomes podem ser modificados e adaptados para cada organização e contexto.
- É uma arquitetura conhecida no mundo back-end então, possuímos muitos posts e referência sobre ela, mas são sobre aplicações back-end.

# Porque eu preciso adotar padrões de projetos?

O conceito de padrões foi primeiramente descrito por Christopher Alexander em [Uma Linguagem de Padrões](https://www.amazon.com.br/Uma-Linguagem-Padr%C3%B5es-Christopher-Alexander/dp/8565837173/).

Padrões de projeto são soluções comuns para alguns problemas típicos, mas acredito que não deva ser algo que seja uma organização de arquivos que fica mais bonita ou que agrade a preferência do programador que está construindo, porque tudo isso é sempre do ponto de vista de quem implementa. Para, ser imparcial durante a definição de um bom padrão de projeto eu levo alguns pontos em consideração de que ele precisa cumprir:

- **Testabilidade** — Que seja possível implementar diferentes testes (unitário, integrado, E2E, mutação, regressão e etc…), precisa ser fácil testar, a porcentagem de coverage não significa qualidade de teste e muito menos de software, porém quanto mais difícil de testar mais cenários os desenvolvedores irá deixar de testar ao longo do caminho.
- **Manutenabilidade** — A alteração de algo em funcionamento, não deveria afetar muitos lugares do organismos. Abstrações e camadas podem parecer burocráticas e verbosa, mas elas garantem a manutenção, isolamento do contexto e sua sanidade no futuro.
- **Escalabilidade** — Uma aplicação assim como uma cidade precisa ser preparada para abrigar muitos cidadãos (usuários) e os engenheiros que irão mantê-la (devs). Não é só escalar apenas código, é escalar a capacidade de muitas pessoas atuarem no mesmo projeto, um projeto grande com muitas pessoas, irá enfrentar problemas como: CI/CD, Ambiente de deploy, PRs e merges, rollback, estratégias de deploy e etc. Para todas as nossas decisões temos que levar isso em consideração.
- **Separação de responsabilidades (SoC — separation of concerns)** — Isolar domínios e responsabilidades, permite um compartilhamento melhor de recursos sem afetar a performance e complexidade da sua aplicação.
- **Observabilidade —** Se algo quebrou, eu preciso saber o que e em qual momento isso aconteceu, preciso atuar e reverter o problema de maneira rápida e eficiente, garantindo que o erro não volte mais a acontecer. Como podemos saber qual é a parte da sua aplicação que mais consome performance? Será que só usar a URL é o suficiente para entender quantos elementos e domínios temos ali e o que eles realmente fazem?
- **Evitar pastas genéricas e sem propósito, shared e utils —** Quando não conseguimos separar bem nossas entidades e camadas, começamos a criar acoplamento entre os recursos, gerando a necessidade de criar pastas para shared ou utils. Funciona para o primeiro elemento, mas depois de um tempo, os desenvolvedores não irão gastar muito tempo pensando sobre o domínio das coisas, e tudo será útil ou shared.

# Entre camas e cebolas.

Uma padrão de projeto de camadas tradicional (layered pattern), o fluxo de dependências entre as camadas segue de cima para baixo. Na camada superior encontramos a área de interface do usuário (User Interface), no meio o processamento das informações (Business), e na base a saída ou entrada de dados para um serviço externo (Data).

![Layered architecture — Arquitetura em Camadas](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6y1fylglht19fyl9ts8z.png)


Fazendo uma sim analogia com uma estrutura em React:

![Exemplo de composição de componentes com React.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/105c5eln3qbbngxsyshn.png)

O grande ponto sobre esse padrão é o forte acoplamento entre as camadas, uma vez que é preciso substituir qualquer uma delas, as outras camadas relacionadas sofrem uma grande alteração ou conflito de comportamento. Ou seja, imagina que precisamos mudar a camada de Data de um Rest API para um GraphQL, a camada de business sofreria diretamente com essa mudança, além da baixa reutilização dos mesmos recursos em lugares diferentes.

Agora invertemos a ordem das dependências, colocando como centro a nossa camada de Business e as camadas de Data e User Interface como referências ao business. Temos uma forma de garantir que, o que é importante para nossa aplicação, fique segura de mudanças garantindo que as camadas externas fiquem responsáveis por lidar com o mundo exterior.

![Inversão das camadas levando o business como principal referência.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u6ei297nipe81b07q7vm.png)

Vamos mudar um pouco mais o formato do desenho:

![Convertendo o desenho da camada para um primeiro formato da nossa cebola.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rzvs5904uqp4z1ovypx6.png)

## Dessa forma, o que temos de evidência?

- O Foco é a parte central da nossa aplicação **o business**, é onde a nossa mudança de negócio e evolução irá acontecer ao longo do tempo. Em alguns casos é possível manter uma linguagem mais pura, sem muitos recursos atrelado a um framework.
- User Interface e Data são camadas periféricas, onde os recursos são substituíveis e podem ser atrelados a um recurso ou tecnologia.

# Evoluindo nossa cebola

Podemos quebrar ainda mais as camadas dividindo responsabilidades:

![Onion Architecture — Arquitetura Onion](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oauzk06rrn1ws7lnoplt.png)

## A camada de user Interface se torna:

**Presentation** — Essa é a camada relacionada ao dispositivo do usuário, seja para web, navegador ou mobile. Os controles de rotas, parâmetros de urls, diagramação visual da página e composição de múltiplos domínios, devem ser tratados aqui. No caso de uma aplicação de front-end os componentes de UI também se encontram aqui, ou seja tome cuidado ao utilizar contextos de domínios em componentes que são apenas UI como Button por exemplo.

## Business é composta de:

**Application** — Essa camada do Business é considerada como operador do domínio, conhecido por alguns como os “Use Cases (Caso de usos)”, são métodos ou componentes que cumprem uma função de controlar o estado do domínio.

**Domain** — Já na camada de domínio, encontramos o controle das nossas principais entidades. “Todos os objetos de uma aplicação deveriam ser uma entidade?” A resposta é não necessariamente, quando você se encontra em uma situação que precisa compartilhar a mesma informação entre dois organismos distintos e vizinhos, você se esbarra no problema da Mutabilidade, logo conseguimos resolver isso com uma entidade Imutável que é parte do domínio. Se o seu domínio for um método de composição de objeto ao invés de um objeto em si também funciona, é um comportamento muito comum em uma estrutura de Functional Programing.

## A camada de Data é separada em:

**Persistence** — Nem todas as aplicações que trabalhei precisou dessa camada, mas cada vez mais tempos aplicações front-ends que permitem o usuário utilizar recursos offline. Logo precisamos persistir um dado primeiramente no dispositivo do usuário, antes de enviar para o back-end. Essa camada abstrai a forma que persistimos essa informação, seja através de um Index DB, Local storage ou memória volátil. Novamente o nosso domínio não deveria se importar quem e como essas informações chegam ou saem.

**Infrastructure** — Aqui configuramos a camada de serviço e adaptadores, uma forma de criar métodos e clientes para controlar nossas chamadas com o back-end, seja ela através de Rest API, Web Assembly, GRPC ou qualquer outro recurso, novamente nosso domínio poderia apenas esperar uma promessa de dados seja como for, a camada de infraestrutura deve se resolver para passar esse acordo. Outra coisa bacana, podemos adicionar aqui transformadores e compositores, ou seja nossa entidade do domínio pode ser um objeto que para existir, precisa ser composto por uma ou mais APIs e modificações em dados, na falta de um BFF, a camada de service trabalha muito bem removendo essa composição do seu domínio e entregando um dado mais puro para as camadas de dentro da cebola.

# As pastas e arquivos precisam respeitar esses nomes?

Não necessariamente, é claro que se estiver com esses nomes facilita a identificação dos contextos por aqueles que já conhecem esse padrão. Porém a adoção dos padrões é um acordo entre os desenvolvedores, e para todos os projetos que implementei, a nomenclatura dos organismos ainda foram o ponto de resistência à mudança pelos desenvolvedores Front-ends. Então minha escolha foi manter os organismos com nomes já conhecido pela comunidade, e criar um dicionário explicando onde se encaixa cada um deles e seus respectivos papéis para o projeto.

Para entender melhor como cada camada ou elemento disso se comporta e funciona, leia a parte 2 dessa publicação.

# Pontos positivos

- Essa estrutura permite tomar decisões mais tarde, ou seja, podemos desenvolver uma feature ou módulo, sem precisar esperar a API ficar pronta, conseguimos criar um mock dos dados e mudar isso no futuro sem afetar o domínio.
- Facilita muito para testar e conseguir garantir uma boa cobertura e qualidade de test.
- É fácil identificar a quebra de um módulo e features em pequenas atividades e PRs.
- Exclui a necessidade de pastas como shared, utils e recursos que são uma gaveta de quinquilharias, no começo do projeto faz sentido, depois de um ano não sabemos se tudo que tem lá é realmente para ser compartilhado ou útil.
- Essa estrutura funciona para qualquer tipo de linguagem ou tecnologia, já usei com projetos Angular, React + NextJS, React, React + Gatsby, VUE e etc.

# Pontos negativos

- A curva de aprendizado é um pouco mais demorada, é uma estrutura robusta e complexa que exige um entendimento do porquê das coisas.
- Antes de desenvolver um domínio é preciso gastar tempo pensando como ele será, o que é relacionado ao seu contexto e o que não é.
- Possui camadas de abstração mesmo que pareça redundante.
- Diferente do Java que é uma linguagem que possui recursos como “Protected”, que consegue bloquear o uso indevido de algumas funcionalidades, o javascript é modo “Freestyle” onde tudo é possível, mas nem tudo deveria ser permitido. Então criar bons padrões de alias, ajuda a identificar quando alguém está usando algo de forma errada.

------

# Referências

Código de exemplo:
https://github.com/RobsonMathias/frontend-onion-architecture

Fontes de estudos:
https://refactoring.guru/pt-br/design-patterns
https://code-maze.com/onion-architecture-in-aspnetcore
https://jeffreypalermo.com/2008/07/the-onion-architecture-part-1