Iniciando uma aplicação de frontend a partir de um monsterlito

Rafael Mariano
Engenharia Arquivei
9 min readJul 5, 2018

Há dois anos me surgiu a oportunidade de trabalhar em uma Startup em São Carlos e todo o meu portfólio até o momento era relacionado à construção de APIs com Ruby on Rails e frontend em AngularJS. Naquela época eu realmente acreditava que tinha domínio em Javascript e achava AngularJS confuso, sobretudo pelo fato dele estar amarrado ao padrão MVC. Também já tive problema com o data-binding do framework quando tive que consumir uma API muito complexa (praticamente um ERP). Eu já tinha trabalhado com AngularJS por mais de dois anos, sendo um deles durante a minha iniciação científica.

Como eu estava no início da minha carreira, eu acabei topando trabalhar como full-stack na Arquivei codando um monolítico bem grande escrito em PHP (Zend). Em paralelo eu estudava React, pois AngularJS já tinha me decepcionado e não era mais hype. Acreditem, para quem está no início de carreira tecnologia hype é algo muito importante.

Eu fiquei impressionado com a curva de aprendizagem do React, pela sua simplicidade e pelas discussões que rolavam sobre javascript isomórfico em toda a comunidade javascript. Resolvi criar uma POC para mostrar para o resto da equipe uma saída para separar o Backend do Frontend no monsterlito existente.

Na equipe de engenharia do Arquivei existe um evento quinzenal chamado "The Pitch" onde algum programador apresenta para o resto da engenharia uma ideia, uma POC ou até mesmo uma coisa que seja legal voltado à tecnologia. Esse evento é o momento perfeito para promover inovação na empresa. Eu usei este espaço para mostrar o projeto para o resto da equipe e o CTO resolver acelerar o processo de especialização em backend e frontend.

Por que usamos Javascript Isomórfico?

Quando falamos de frontend, ele está diretamente ligado à Single Page Application. Em resumo, em uma SPA um arquivo html estático é usado referenciando os assets da aplicação. Depois disso é só ficar mudando o seu javascript principal e pronto, você não precisa lidar com deployment e nem com gerenciamento de versão de assets. Basta sair digitando código.

Na minha visão é sempre bom manter boas práticas de código. Por ser tão fácil de iniciar um projeto, é fácil fazer uma aplicação com a metodologia GoHorse. Eu mesmo já fiz muito isso.

Optar por um estratégia dessa é muito fácil, pois basicamente não existe um pensamento criativo sobre a tecnologia. Além disso, você liga o foda-se para a performance do usuário e assume a premissa de que o seu Javascript é perfeito e nunca vai quebrar (inclusive no IE).

O primeiro ganho com a arquitetura isomórfica foi separar o Time to Render do Time to Interact. Em um modelo SPA não existe essa separação e o resultado é uma tela em branco por um certo tempo. Quando há um server side rendering com jQuery é comum que haja uma render com a maioria dos elementos e depois um javascript que altera algumas vezes o DOM. A primeira abordagem é realmente frustrante, pois a página demorar para carregar. Sobre a segunda abordagem, eu fiz uma série de análises de performance e cheguei na seguinte conclusão:

  • O primeiro render até que era rápido, porém incompleto;
  • O número de vezes que uma alteração forçada acontecia era realmente elevado e isso fazia com que o tempo de interação fosse muito grande;
  • Apesar de ter uma prévia visual, o tempo da renderização final era muito próximo do Time to Interact.

Perfeito! agora temos uma série de apelos para seguir com o projeto de frontend:

  • Performance, uma vez que o usuário receberá a página final num tempo muito menor;
  • Separação do tempo de renderização do tempo de interação;
  • Especialização da equipe, uma vez que ela manterá um foco total em Javascript + HTML + CSS;
  • Um único ponto de falha, uma vez que o código que renderiza o HTML no server-side é o mesmo executado no client-side;
  • Agilidade tanto pela especialização quanto pelo isolamento das responsabilidades de cada código.

State Machine

A proposta desta mudança (e especialização do frontend) era de que a equipe de backend desconhecesse completamente qualquer código Javascript, HTML e CSS. Para fazer isso foi necessário especificar um "contrato" entre as duas equipes. Em resumo, a equipe de backend faz todas as regras de negócio, monta um JSON com todos os dados e envia para o serviço de frontend.

Do lado do frontend há um store de dados que mantém todos os dados da aplicação e os conecta diretamente na visualização. No primeiro momento esse store é exatamente o JSON que o backend envia para o frontend. Para gerenciar o store de dados usamos o Redux. Para aprender o Redux recomendo o curso do EggHead pela sua simplicidade e objetividade.

O store de dados funciona exatamente como uma máquina de estados onde recebemos as variáveis que ditam o comportamento da visualização e este é totalmente fora do controle do React. Isso faz com que a visualização seja algo bem atômico e muito próximo de uma função pura. A maioria esmagadora dos componentes que escrevo são stateless.

Para exemplificar, existe uma árvore de dados (objetos ou hash map para os aspirantes em javascript) com vários níveis e, em algum lugar dessa árvore, eu tenho uma variável que indica se uma chamada Ajax foi feita e ela está em andamento. Vamos chamar ela de loading. Com isso, a nossa visualização recebe esse dado de forma direta e consegue bloquear a re-tentativa de um Ajax ou fazer uma animação de carregamento para um determinado contexto.

Arquitetura de Store-View

É claro que no primeiro momento não pensei em usar o redux de forma tão eficiente. Usei o Redux apenas como uma ponte entre os dados do backend e um componente raiz. Depois que o componente raiz recebia o dado ele hidratava os demais componentes sangrando os dados até o ponto onde era de fato usado.

Essa abordagem era horrível pois eu mesclava muito o state interno do componente com o store de dados o que tornava bugs irrastreáveis. Se você está usando essa abordagem tome cuidado com o seu projeto pois tivemos muita dor de cabeça com isso 🤕.

Para isolar a visualização dos dados separei a aplicação em duas camadas: a camada de dados e a cama de visualização. Além disso, uma pequena interface de conexão entre as duas pontas.

Em resumo, do lado dos dados o store é totalmente imutável com auxílio do ImmutableJS e só possível transitar ou extrair os dados através de uma interface bem definida.

Na camada da visualização ainda existe um componente raiz (até porque eu acredito que seja a única forma elegante de injetar o react na aplicação) que representa uma rota e as parcelas desta rota são quebradas e hidratadas via connect do React-Redux. Dentro dessas parciais existem componentes que são divididos entre atoms, molecules e organism (chamamos de components mesmo).

A seguir deixo a visão da nossa arquitetura final. Ela evoluiu muito ao longo do tempo e está estável há pelo menos seis meses.

O micro-serviço de renderização

A maior dificuldade de migrar uma aplicação monolítica é manter a consistências na interface que o usuário vê. Além disso, é realmente embaçado tentar criar um novo design system durante a migração.

Quando iniciamos o projeto, copiamos os arquivos LESS do monolito para a nova aplicação e seguimos todo desenho da aplicação usando estilos como era usado no monolito. O problema é que o CSS não era incorprorado no HTML. Isso significa que se houvesse algum problema nos assets a visualização simplesmente quebraria ou até mesmo lentidão no carregamento dos assets já que ele é servido em serviço de terceiro, que no meu caso é o CloudFront.

Para evoluir a arquitetura para um modelo isomórfico completo seria necessário a mesclar CSS dentro dos nossos componentes e que não fosse feito inline, que é uma abordagem bem rústica. Além disso, seria ideal usar algo com as características de um pré processador CSS como o LESS, SASS ou SCSS. Não precisamos perder muito tempo para encontrar a mágica chamada styled-compontes. Essa é uma biblioteca bem simples e robusta para incorporar CSS com JSX. Além disso, ele é passível de ser usado no lado do servidor (perfeito não?)

Essa é visão final do HTML gerado. Nela fica bem claro que todos os estilos são incorporados diretamente no HTML, o estado inicial está em uma variável global. Todo o HTML final está dentro do body. Logo no começo foi inserido como comentário a versão do backend e a versão do frontend. Reparem que nossa versão não referência mais major, minor e patch. \o/ae

Quais os desafios de hoje?

Uma vez que temos muito bem definida a arquitetura e a stack já é possível migrar todo o monolito para a arquitetura backend+frontend. Acredito que esta migração demore cerca de 1~2 anos para ser parcialmente concluída uma vez que não pretendemos migrar todas as telas, algumas delas são legadas ou tem um número de visitas muito baixo.

Por fim, esperamos construir o nosso próprio Design System e sair completamente do Bootstrap, criando uma interface bem característica da Arquivei e deixando os nossos componentes facilmente portáteis para outros projetos. Uma das vantagens de criar um design system próprio é fazer um isomorfismo completo e fazer com que o design seja estrangulado e consistente evitando erros por parte do programador.

Para fazer isso nós estruturamos os projetos em um mono-repositório que compartilha os componentes. Mas isso é um assunto para um próximo artigo.

Conclusões (Pros e cons)

A performance ficou muito mais clara uma vez que separamos diretamente o Time to Render e o Time to Interact. O Time to Interact ficou um pouco mais lento já que aumentamos as dependências externas (antes era só jQuery) tanto em tamanho quanto em complexidade. Porém o nosso Time to Render é muito menor do que o do monolito. Ele acontece antes mesmo de começar o download do javascript.

A equipe ficou bem especialista no frontend. Evoluímos muito bem a arquitetura e isolamos as responsabilidades dos componentes. Conseguimos manter um foco muito bom em performance e em usabilidade. Também rastreamos bugs com mais precisão já que é possível reproduzir a tela do usuário em um ambiente controlado. Nós usamos o Sentry para fazer debug runtime, então conseguimos capturar com precisão os bugs que acontecem do lado do cliente sem nenhuma interação com o mesmo.

O deploy ficou muito mais complicado já que houve a necessidade de criar um micro-serviço de renderização que é de responsabilidade da equipe de front. Nós tivemos que controlar toda a política de cache e de versionamento de assets. Esta foi uma tarefa muito difícil, mas que promove uma performance significativa no carregamento dos assets da aplicação.

A curva de aprendizado do React é realmente impressionante. A do Redux nem tanto já que o conceito de máquina de estados causou um nó na cabeça dos novos desenvolvedores e nos "colaboradores". ImmutableJS e Styled-componentes deixou a aplicação mais robusta e não foi um barreira para os novos desenvolvedores, acredito que estas bibliotecas deram mais agilidade para o time.

A equipe consegue criar protótipos de alta fidelidade uma vez que o micro-serviço de renderização funciona sem a necessidade de um backend, bastando simular o JSON de entrada. Isso tornou possível a criação de um projeto "Conta Dummy", uma espécie de backend fake, sem qualquer envolvimento do backend.

O contrato com o backend ficou bem simples e a cargo da equipe de frontend. Quando o backend precisa fazer alguma mudança ou um dado mude de nome somente o selector e o reducer são modificados no processo.

A migração das telas do monolito acontece em etapas, desta forma conseguimos criar novas telas conforme a necessidade e migrar somente aquelas necessárias. O backend faz a lógica de negócio e pede o HTML para o micro-serviço de frontend.

A stack React + Redux + Styled-components + NodeJS + Webpack foi fundamental na criação da nossa plataforma isomórfica. Hoje em dia existem frameworks que trabalham com essa abordagem de maneira muito simplificada e com pouco código, tal como o NextJS.

Encorajo fortemente os programadores a partirem para esta abordagem visto os benefícios que esta traz. Em breve farei um post explicando a experiência que tive com o NextJS e quais as semelhanças/diferenças com a nossa aplicação.

--

--