E Ottaviani Aragão Blog Projetos Sobre Mim

Arquitetura e Camadas de abstração — Uma proposta para Front End — Parte 2

No post anterior, eu comentei sobre as camadas de abstração que eu vejo como recorrentes nos projetos que trabalhei. Mas de fato não é suficiente para classificar como uma arquitetura, porém é fundamental para que eu possa dizer quais são as abstrações que vão compor essa arquitetura.

Quais as vantagens?

Eu acho que essa deveria ser a primeira pergunta que deveríamos fazer antes de pensar em algum padrão. Aqui eu vou divergir um pouco do Clean Architecture no contexto do Front End. Eu vejo que a preocupação inicial do Bob Martin é desacoplar ao máximo a aplicação a ponto de poder, em algum momento, migrar certas tecnologias no projeto.

Mas a minha dor é outra, quase nunca me vi nesse cenário onde um sistema precisava ser atualizado por conta apenas da tecnologia, na maioria das vezes isso foi feito porque o sistema anterior estava mal arquitetado.

Existe um fenômeno que acontece hoje que é o alto índice de Turn Over que as empresas de tecnologia possuem pela alta demanda de profissionais capacitados e dificuldades no dia a dia na retenção de talentos, isso também contribui para a necessidade de migrar projetos para uma tecnologia mais utilizada de mercado, para ajudar na retenção, para ser mais atraente para os profissionais que estão no mercado e para diminuir também a curva de aprendizado para os novos colaboradores.

Embora o meu objetivo esteja na linha de tentar desacoplar ao máximo e poder contribuir para simplificar uma possível migração, a minha intenção é tornar o sistema previsível, organizado e fácil de sofrer alterações. Então o que eu me preocupo não é com a migração do sistema, a minha maior preocupação é com refatoração e novas funcionalidades. Para mim são estas duas ações que costumam acontecer de maneira mais frequente.

Além disso, sabemos que muitos sistemas hoje em dia estão crescendo de maneira a serem quebrados em várias aplicações que se conversam. Muitas vezes times diferentes trabalham nestas aplicações e há uma necessidade hoje de deixá-las flexíveis para que possam ser desenvolvidas em tecnologias diferentes ao mesmo tempo que seja possível também compartilhar soluções.

Então, fazendo um apanhado das idéias, motivações e objetivos, na minha cabeça a arquitetura deveria….

Aumentar as chances de se criar um sistema flexível de fácil manutenção, possuindo restrições para garantir a previsibilidade e mais agnóstico, podendo derivar-se para diferentes times com diferentes tecnologias.

Os papéis de cada camada nas vantagens da arquitetura

A idéia é que percorramos cada uma as camadas na direção da menos acoplada à mais acoplada ao sistema.

Como eu disse anteriormente, a arquitetura precisa ser agnóstica e podendo derivar para diferentes tecnologias. Para a gente conseguir fazer isso a gente precisa identificar quais camadas podem ajudar nessa interoperabilidade, ou seja, quais delas podem ser usadas em quaisquer sistemas web.

As tecnologias obíquas hoje na web são as mesmas das da década de 90, são elas : HTML, Js e Css.

As camadas que são escritas em linguagens que geram ao seu final com poucas ou nenhumas dependencias ou definições de regras de negócios são…

UI System e Helpers & Utils ( Camadas base )

Estas são as camadas Base do seu sistema Front-end Web. A idéia é desenvolver de maneira mais desacoplada do sistema possivel estas duas camadas, talvez separando-as em pastas específicas e no caso do UI System, uma maneira de gerar um ui-system.css.

Os helpers & utils como dito na parte 1 desta série, são apenas um conjunto de funções que podem ou não serem puras, mas que endereçam problemas de propósito geral, elas partem do princípio que vão rodar no ambiente do browser, então todas as variáveis globais de acesso estão, de certa forma, garantidas dentro destas funções, podendo ser exportadas para mundo através de um pacote npm.

Então se você criou seu boilerplate em React, e exporta ui-system.css e possui um lugar que armazena todas as suas funções utilitárias sem dependencia de outros itens de seu sistema, vai ser muito fácil você exportar através do npm criando módulos podendo ser reutilizado em outro projeto. Se um outro time quiser desenvolver utilizando Angular, você já vai ter uma boa parte do seu sistema pronto para ser usado.

Services & Entities ( Camadas de dados )

Andando mais pra frente na nossas camadas com relação ao acoplamento, temos duas camadas que possuem um grau de acoplamento um pouco maior do que as anteriores. As camadas de Dados. Estas definem quais os modelos de dados irão existir nas suas aplicações e quais api's e bibliotecas irá utilizar para as requests. De forma gradual estamos entrando um pouco mais pra dentro de definições de negócio e tecnologia do seu sistema.

Porém elas podem ainda ser classificadas como camadas que possuem uma linguagem obíquoa, que é o Javascript no contexto do browser e talvez até no Node. Como dito no capítulo anterior, os serviços são funções javascript que recebem argumentos e devolvem uma Promise, esta estrutura de dados está presente na maioria dos browsers mais modernos e no node.

Portanto, lembrando daquela situação anterior, onde temos um outro sistema desenvolvido em Angular, se necessitássemos reutilizar as chamadas de serviços que podem ter bastante complexidade por conta das composições entre outros serviços e BFF's, poderíamos fazer isso desacoplando esta parte da aplicação como faríamos no caso das camadas base, extraindo da aplicação e exportando através de módulos npm / repositórios.

As entidades poderiam caminhar na mesma linha pois são apenas funções que recebem dados e retornam dados formatados em uma estrutura mais conveniente.

Stores & Components ( Camadas da aplicação )

No final da nossa caminhada, estão as duas camadas finais que eu chamo de camadas da aplicação. Isso porque aqui estará o maior grau de acoplamento que teremos, com detalhes com relação à regras de negócios, bibliotecas, etc. Não há aqui uma necessidade grande de viabilizar a reutilização de componentes e stores embora isso possa ser possível para sistemas com as mesmas características e tecnologias, talvez serão raros os momentos que conseguirá isso, além do fato de que a reutilização componentes entre tecnologias diferentes talve seja inviável ou improdutivo e provavelmente seja uma tarefa para outra tecnologia de desenvolvimento de componentes nativos e isso se torna um outro assunto.

Começando pela Store, sua vantagem é conseguir testar e concentrar de maneira desacoplada de frameworks as Regras de negócios da aplicação. Então aqui seria muito desejável uma biblioteca de gerencia de estados que fosse agnóstica ou vanilla e que exportasse adapters para os mais variados frameworks para fazer a integração com os ciclos de vida do componente naquele framework específico.

Assim, em uma eventual migração de React para Angular ou vice e versa, óbviamente teria um certo trabalho para reescrever os componentes de uma tecnologia para a outra, porém as regras de negócios estariam intactas e prontas para serem testadas no novo sistema, e acredito que seja geralmente nesse contexto que os bugs da aplicação nova acontecem, porque penso que os detalhes das regras de negócios geralmente ficam acopladas nos componentes visuais que acabam sendo violadas na migração do componente e na cabeça dos desenvolvedores que podem nem estar mais naquele mesmo time ou empresa.

Aqui é uma divagação minha, mas ao testar funções independentes de framework e com pouca dependencia, imagino que haja algum ganho de velocidade / performance nos testes, talvez outro ganho nessa separação é poder testar de forma atomica cada parte da aplicação e melhorar a performance do seu deploy.

Mas como eu disse o papel destas camadas de aplicação nem é tanto a reutilização ou migração, mas tentar dar ao sistema mais previsibilidade. Sabendo que as regras de negócio estão centralizadas nas stores, caso tenha algum problema em alguma regra você vai saber exatamente onde procurar essa falha.

Falando agora da camada de Componentes, é aqui onde os frameworks realmente entram. E se você está familiarizado com os discursos do Uncle Bob deve reconhecer que há uma convergência ao pensamento dele nessa proposta, que é a visão de que as decisões sobre detalhes com relação à certas tecnologias, como a escolha do Framework, deveriam ser postergadas para o final do processo e aí você vai perceber que o framework entra depois de termos criado bastante coisa.

Ainda no tema de previsibilidade, faltava eu falar de outro fator das características que havia descrito que essa arquitetura deveria ter, que é definir restrições para que a previsibilidade aconteça agora também no contexto dos componentes.

Restrições e relacionamentos das camadas da aplicação

Bom, aqui a gente precisa definir certas restrições, dizer como devemos fazer o relacionamento entre as camadas e componentes da aplicação. Caso contrário, tudo isso que descrevemos não será útil, pois o projeto estará fadado à ser exatamente como todos os outros que gostaríamos de ter desenvolvido melhor.

Acho importante dizer, a arquitetura apenas olhando no macro não será capaz de evitar problemas de relacionamento nos sistemas, temos de olhar no micro também.

O micro no qual estou falando, refere-se à relação entre os componentes do nosso sistema. É importante definir e lutar para seguir algumas regras:

  1. Um componente pai pode se relacionar com um componente filho.
  2. Para se relacionar com outro componente satélite, este relacionamento precisa ser mediado por uma store da aplicação.
  3. Componentes podem interagir diretamente com as stores e se guiar pelas regras de negócios que ficam concentradas na Store.
  4. Componentes podem interagir com os Services consumindo dados já formatados e estruturados pelas Entities.
  5. Componentes podem consumir diretamente os helpers & utils.

Algumas dessas regras você já deve conhecer, e como eu disse, não quero inventar algo novo, mas me beneficiando de conceitos que já existem e funcionam bem.

Cabe também acrescentar outro padrão conhecido principalmente para quem desenvolve em React. Se o seu framework é um framework UI como é o caso do React, vale a pena utilizar o padrão de Dumb & Smart Components.

Eu costumo chamar estes componentes Dumb de View Components e os Smart de Controller Components, trazendo um pouco da filosofia MVC aqui, este conceito funciona bem para qualquer framework hoje em dia.

A diferença entre eles na minha visão é :

  • View Components estariam apenas preocupados em alteração de estado e tudo pode ser injetado via props, sem nenhuma chamada externa, a não ser alguma que fosse dedicada a visual, são os mais prováveis a serem reutilizados globalmente na sua aplicação.
  • Controller Components controlam o fluxo de dados da aplicação entre os componentes filhos, dados externos ( Services ) e regras de negócios ( Stores ).

Talvez você ache útil na sua aplicação separar em pastas diferentes para diferenciar estas duas definições de componentes, aí é contigo.

Nem sempre será óbvio quando fazer a administração de certos estados em um componente pai ou fazê-la diretamente em um componente filho acessando diretamente uma Store. Você vai precisar testar e ver o que se adapta melhor para a situação, e a idéia da arquitetura é que seja fácil de fazer essas alterações.

A idéia principal da Store é resolver o Prop drilling, uma dica que o sistema te dará que é hora de plugar seu componente diretamente na Store ao invés de receber as props do componente pai, é quando percebe que para cada adição de uma propriedade a mais em um determinado componente, precisa atualizar 3 ou mais componentes como consequência.

Bom, agora só falta fazer né ? É interessante ter ama implementação para poder validar e também refutar certas idéias. Então na próxima parte vou tentar implementar uma solução simples à princípio, usando aquela idéia que dei como exemplo no post anterior, um sistema de carrinho básico, onde existe um limite de itens que posso adicionar dado meu tipo de usuário.

Vamos ver como essa arquitetura se comporta e como podemos pensar na implementação à nivel de estrutura de pastas também.