Cebolas e camadas para padrões de projetos no Front-end — Parte II

Robson Mathias
Tino Tech

--

Essa é a segunda parte de uma publicação sobre uma arquitetura onion para projetos front-ends. De uma olhada na Parte I para entender alguns contexto e composição antes de seguir com a leitura.

Relembrando o papel de cada camada

Presentation — Camada de Páginas e componentes UI, o acesso mais próximo ao controle de rotas de uma aplicação e recursos visuais sem relação um domínio.

Persistence — Camada de persistência de dados, nem todas aplicações possuem, mas no caso de uma aplicação que guarda dados para uso offline é aqui que iremos controlar esse fluxo.

Infrastructure — Estrutura e composição de comunicação com o mundo exterior, como APIs, back-ends, storages e etc…

Application — Camada de controle do domínio.

Domain — Controle de entidades importantes para o seu negócio.

Arquitetura Onion

Layouts (Presentation) — Essa pasta possui apenas componentes de composição de UI, componentes visuais genéricos que não carregam referência ou comportamentos de domínio, como: Button, Typography entre outros. Mesmo que você utilize uma biblioteca de UI Kit pública ou privada, vai precisar dessa pasta, porque podemos ter uma sobrescrita de comportamentos ou até mesmo componentes específicos de cada projeto que é compartilhado por vários módulos.

Pages (Presentation) — Composição e manipulação de rotas e páginas, uma página pode ser capaz de compor sua diagramação de tela com css, textos e outros recursos. Ela é responsável por acessar os parâmetros de rotas e passar para os módulos quando necessário.

Wrappers (Application) — Este pattern é um definidor de boundaries, ele permite que você crie camadas para isolar pedaços da sua aplicação obrigando a se comportar de uma forma específica. Exemplo, podemos criar um “with-auth” (Com autenticação) com definições de bloquear qualquer acesso ou execução da aplicação no caso da falta de um token de acesso.

Modules (Application / Domain) — Nossa aplicação será separada em módulos, grandes organismos que possuem um conjunto de funcionalidades (features), que cumprem o mesmo propósito ou propósitos complementares. Exemplo, vamos supor que temos uma aplicação To-Do List (Lista de tarefas). Criar, Listar, Atualizar e Deletar uma tarefa está relacionado ao mesmo propósito de gerenciamento da entidade de Tarefa, logo temos um módulo de tarefas com 4 features. Um módulo pode ter sub-módulos?Sim, mas acho pouco provável, normalmente nessa situação, acredito que nessas situações podemos começar a criar uma sobrecarga de propósito para o módulo, e vale a pena uma discussão com o time, se isso merece uma reestruturação ou quebra do módulo — (SoC — separation of concerns) .

Modules/Features (Application e Domain) — Como dito anteriormente, um módulo pode ter N features que irá cumprir um propósito relacionado ao seu domínio. Exemplo, considerando o módulo de Authentication, ele pode possuir features como: Login, Sign Up, Sign In, Forgot Password, Reset Password e Update Password, Update Email, Sign In With (Google, Facebook) e etc….
Mas e o Update Profile? Acredito que a atualização de informação do perfil não afeta o domínio de acessar uma aplicação, logo essas features deveriam estar relacionadas ao domínio User ou Client.

Modules/Features/Container (Application) — Containers são os orquestradores, no caso do React, são componentes que não sabem criar uma camada de lógica, sabe somente executar e chamar os responsáveis para isso, é como um regente de orquestra, ele não sabe tocar flauta, violino e o sax, mas consegue pedir para cada um tocar ao seu tempo e sua parte em específico criando uma harmonia musical. Ou seja, é ele que coloca o Hook e Component para se integrarem e trabalharem juntos.

Modules/Features/Components (Application) — Essa parte é a mais rasa da Application, ela traduz a interação do usuário para o domínio. Exemplo, no caso de um formulário, toda regra e composição dos elementos visuais estariam nesta camada, lembrando que, ela não tem acesso a store, hooks ou qualquer parte do domínio, todos os inputs e outputs de interação precisam ser passado por props, isso facilita e muito o teste unitário e garante uma cobertura muito boa de todos os cenários possíveis que esse elemento possui, além de facilitar bastante os testes de regressão e mutação.

Modules/Features/Hooks (Application) — O nome Hook é um padrão muito utilizado no react, onde permite a declaração de recursos que um componente pode utilizar, para qualquer outra linguagem podemos conhecer como providers ou simplesmente funções. Essa camada possui métodos de ação que estão relacionados ao controle do domínio.

Um hook pode despachar eventos ou controlar a mudança do mesmo domínio, mas cada um tendo o seu contexto e composição isolados. Exemplo, o módulo de Authentication, podemos ter funções como useLogin, useSignUp que divide a mesma entidade, afinal, o resultado de ambas request receberíamos um token para acesso das APIs.

Modules/Features/Store (Domain) — Essa é a camada mais importante da sua aplicação, toda tela ou funcionalidade é comporta por eventos de interação e controles de entidades. Uma informação modificada em uma entidade de domínio, pode afetar diversos ecossistemas ao longo do projeto, por isso centralizar a mudança dessa entidade em apenas um único lugar garante o problema de mutabilidade que aparece ao longo dos projetos. Não necessariamente é preciso o uso de uma store, objetos globais ou algo do tipo. É possível montar um domínio baseado em eventos, transação entre Listeners (Ouvintes) e Providers (Provedores) ou outras estratégia, o importante é garantir uma centralização da mutabilidade do contexto.

Service (Infrastructure) — Composição de métodos e entidades que irão fazer a camada de envio e recebimento da camada exterior da aplicação, mas um service ainda não é a parte final, ele não deveria saber se está executando um fetch, salvando em um Index DB ou local store. Ele conhece um adaptador que faz essa chamada, e ele sabe compor um contrato de conversa com sua camada de aplicação e mundo exterior, ou seja, se para fazer um upload de um arquivo é preciso fazer um GET e POST juntos, o método da camada service sabe fazer isso e retorna uma única promessa quando os dois acabar, assim o seu Hook só precisa saber que o método ArquivoService.upload(file), sabe retorna uma promessa e receber um arquivo, escondendo assim como funciona a camada de comunicação com o mundo exterior.

Service/Transform (Infrastructure) — Essa camada de transformação, permite que editamos os arquivos que a camada exterior retorna ou antes do envio e é algo não atrelado a um domínio, normalmente essa camada é muito utilizada na falta de um BFF (Back-end for Front-ends).

Adapters (Persistence) — Esse recurso pode parecer redundante, mas ele permite que você descreva um comportamento padrão para os recursos externos a sua aplicação como o HTTP, GA (Google Analytics), GraphQL, Index DB ou qualquer outro, forçando a todo mundo falar a mesma coisa da mesma forma, assim o seu service pode passar a aceitar qualquer um desses recursos sem precisar identificar qual é o recurso que ele está lidando.

Dependência entre módulos

Bom agora, que temos uma descrição e composição dos ecossistemas, como módulos vizinhos se comunicariam e teriam contato?

Para melhor explicar como isso funciona, vamos ilustrar dois cenários diferentes:

1 — Dependente, formulário de cadastro (Sign Up) — Precisamos compor uma tela onde o usuário obrigatoriamente insira um nome, email, senha e aceite os termos de contrato.

A feature de Sign Up do domínio de Auth precisa obrigatoriamente do aceite do termo, ou seja, temos uma dependência direta entre domínios. Um não funciona sem o outro, logo o Container de Sign up pode importar diretamente esse recurso, e utilizar a store e hooks para controle se for necessário.

Endpoints que compõem o formulário de cadastro.

Para o aceite de contrato precisamos de uma endpoint que retorne qual é o ID do contrato que ele aceitou e viu para que possa ser enviado no endpoint de cadastro. Uma vez que temos um recurso de aceite de contratos, identificamos que futuramente teremos mais contratos a serem aceitos além do sign up, outros recursos e telas precisarão de um comportamento parecido. Sendo assim temos aqui claramente dois domínios: Auth e Terms.

Formulário de cadastro e seus domínios
Página de cadastro com a implementação dos imports e separação dos elementos.

2 — Não dependente, Tela de listagem e criação de tarefas (Tasks To-Do List) — Veja, eu posso ter uma listagem de tarefas e não é obrigatório ter um componente ou recurso que crie as tarefas na mesma tela, uma coisa não depende diretamente da outra. Talvez tarefas podem ser criadas ao integrar com sua ferramenta de tarefas do Google e assim adicionar para você, uma vez que não temos domínios diretamente acoplados por regras de negócio ou existência, podemos deixar a camada de página compor esses domínios e recursos. Assim reduz o acoplamento entre escopos isolados os propósitos de cada um.

Endpoints que compõem as features de listagem e criação de tasks.
Features de Criação e listagem de Tasks.
Página que compõem separadamente cada feature do módulo de task.

Pontos positivos

  • 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.
  • Para quem trabalha com sprint ou kanban que precisa quebrar algumas histórias, essa arquitetura entrega uma receita e ordem de entrega muito fácil: Service -> Store -> Hooks -> Component -> Container e por fim Page.
  • 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.

Pontos negativos

  • 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.
  • Possuí duas formas de encontrar algo no projeto, quando utiliza URL de referência conseguimos encontrar as coisas através da pasta Page. E quando navegamos como referência de domínio conseguimos ir diretamente no Módulo para encontrar os elementos.

--

--