Frontend

Prós e contras na adoção de Microfrontends

A decomposição em microfrontends é uma técnica para dividir monolitos de front end em pedaços menores e mais simples que podem ser desenvolvidos, testados e implantados de forma independente, enquanto ainda aparecem para os clientes como um único produto coeso. Veja os principais benefícios e malefícios na sua utilização.

As arquiteturas de micro serviços (MSA – Micro services architectures) têm sido adotadas pelas organizações que buscam uma melhor escalabilidade horizontal, mostrando que é uma boa alternativa para divisão de responsabilidades, isolamento de falhas e gestão de bases de código menores.

Mas a transição de aplicações monolíticas para arquiteturas de micro serviços frequentemente recai em um problema comum: o famoso frontend monolítico. Além de reduzir os benefícios de uma arquitetura de micro serviços, a abordagem de implementar um único frontend para todas as funcionalidades de um sistema pode ser um desafio bastante complexo de gerenciar sob grande escala.

Imagine um cenário onde N equipes estão atuando em diversas iniciativas em paralelo, sobre a mesma base de código, e seu roadmap contempla homologações e entregas previstas para datas descasadas. É inevitável que você terá que lidar com, pelo menos, um dos seguintes problemas: 

  • Desperdício de tempo para realização de merges e resolução dos impactos das versões mescladas;
  • Alto volume de tempo para revisar as alterações de código;
  • Decidir quais times homologarão suas features primeiro;   
  • Garantir que a feature entregue por um time A não afete a entrega do time B;
  • Garantir que as demais features estão prontas para subir junto ao publicar a sua feature em produção
  • Garantir que o código que você está validando não foi atualizado por outra equipe após você executar todos seus testes;
  • Reduzir o tempo de compilação, teste e deploy do seu frontend;

Muitos desses problemas são causados pelo fato de haver diferentes times atuando sobre a mesma base de código… Para atenuar alguns desses problemas, algumas práticas podem ajudar:

  • Feature toggle: parametrização que permite desligar features que não estejam prontas para serem publicadas em produção. Vale lembrar que feature toggles geram complexidade de código, assim como cenários alternativos, que podem dificultar o throubleshooting em produção. Além disso, subir um código desligado para produção, sem ter sido exercitado antes, é uma prática que pode levar ao acúmulo de falhas latentes no sistema.
  • Boa cobertura de testes automatizados que apoiem na identificação prematura dos impactos de mudanças na mesma base de código. Lembre-se que, por maior que seja a cobertura de testes, nem todos bugs serão pegos por eles. Os testes end-to-end são os mais caros da Pirâmide de Testes, e quanto maior a aplicação, mais telas e fluxos alternativos existem para serem testados. 
  • Comunicação e controles de subidas para produção podem ajudar a reduzir o risco das publicações. Porém, em grande escala, pode ser como estar dirigindo um carro com o freio de mão puxado… Ou seja, sempre que você quiser publicar a sua feature, precisaria consultar os demais times que atuam sobre a mesma aplicação, para tentar mitigar o problema de concorrência no desenvolvimento.

Apesar de haver medidas paliativas, alguns casos podem justificar abordagens mais estruturais, que permitam alto nível de paralelismo e independência entre os times, tanto do ponto de vista de desenvolvimento de software, quanto compilação, deploy e execução. Nessas situações, pode ser interessante avaliar a utilização de microfrontends. 

Alguns benefícios chave na utilização de microfrontends são bastante semelhantes aos benefícios de arquiteturas de micro serviços:

Bases de código menores e fáceis de manter

Redução do volume de código por projeto, facilidade na resolução de débitos técnicos, pois o impacto gerado é menor, a quantidade de testes também é menor. Além disso, o isolamento em aplicações diferentes evita o acoplamento demasiado entre os componentes, seja ele intencional ou não intencional.

Organizações mais escaláveis, com times autônomos e desacoplados

Menor esforço para mesclar versões e resolver impactos gerados por elas, menor tempo de revisão de código, redução das dependências entre entregas.

Habilidade de atualizar, evoluir, ou mesmo reescrever partes do frontend de uma maneira mais incremental

Microfrontends permitem testar novos frameworks ou versões de frameworks de forma controlada, sem afetar os demais.

Deploy independente

A publicação de componentes menores e individuais reduz consideravelmente o escopo e, consequentemente, o risco das publicações. Isso quer dizer que, se o time A realizou um deploy com erros, a aplicação do time B seguirá funcionando, e a decisão de publicar a versão pode ser tomada de forma autônoma pelo time B. Além disso, o tempo para realizar a correção, compilação, e execução de testes do componente B será muito menor do que se fosse necessário publicar um frontend monolítico inteiro.

Como toda decisão arquitetural, micr frontends também possuem pontos negativos a considerar, alguns deles são:

Maior overhead

Manter vários componentes de software distintos é uma tarefa mais trabalhosa que manter uma única aplicação. Em outras palavras, existe um overhead associado à gestão de vários repositórios, configuração de pipelines e monitoramento de aplicações implantadas em produção, que deve ser considerado. Se sua aplicação é mantida por um único time, e não houver previsão de essa característica mudar, talvez seja mais produtivo manter uma única base de código.

Compartilhamento indireto de código

Caso você queira aproveitar código entre os diferentes fronts (sejam classes utilitárias ou componentes de UI) pode ser necessário criar bibliotecas compartilhadas que terão sua própria base de código, versionamento e manutenção, já que não teremos a facilidade de simplesmente referenciar os métodos que estariam dentro do mesmo frontend monolítico.

Manutenção da mesma identidade visual em todo o sistema

Folhas de estilos, ou componentes criados em duplicidade, podem ter apresentação diferente para o usuário, podendo prejudicar a sensação de unidade e dificultar o aprendizado. Esse aspecto é dificultado quando cada microfrontend utiliza diferentes frameworks.

Complexidade para garantir a segurança

Dependendo da abordagem de implementação utilizada, é preciso que cada microfrontend receba as informações de autorização de acesso às APIs do backend. Frequentemente as APIs exigem alguma forma de autorização, como um JWT que permita identificar o usuário autenticado, suas roles, entre outras informações. Suponha que esse JWT tenha sido gerado no momento do login e que, durante a navegação no seu micro frontend esse token tenha expirado. Muito provavelmente, você precisará definir algum mecanismo que permita que esse token seja renovado, e que, caso a renovação já não seja possível, redirecione o usuário para a tela de login. Em algumas abordagens, essa lógica precisará executar a partir de cada um dos microfrontends direta ou indiretamente e, talvez, demande a definição de um protocolo de comunicação entre eles. Caso seja necessária a comunicação entre os microfrontends, aspectos de segurança como vazamento de cookies e Cross‑Site Scripting (XSS) devem ser observados.

Faz sentido no meu projeto, como implementar?

No que diz respeito à implementação, os recursos da Web permitem usar e abusar da criatividade. Porém, existem algumas estratégias que podem ser utilizadas como ponto de partida.

Tipicamente, existe uma aplicação contêiner principal, responsável por renderizar alguns componentes comuns a todas as páginas e carregar cada microfrontend no seu devido lugar.

Essa aplicação contêiner pode realizar a junção dos microfrontends de diferentes maneiras. Abaixo estão algumas delas:

Composição de componentes a nível de servidor

A montagem do html ocorre de forma dinâmica já no servidor web. A ideia é desenvolver uma aplicação servidor (por exemplo, NodeJS) que retorne o microfrontend já como parte do HTML para o browser.  A junção dos fronts ocorre em tempo de execução, no servidor.

Prós: Possibilidade de download de combinações de frontends em uma só requisição para o servidor. Evita problemas de integração de fronts de diferentes domínios.Flexibilidade para variar completamente o layout nas diferentes rotas da aplicação.Maior controle sobre o cache de conteúdo na aplicação contêiner (eventualmente, com auxílio de bancos de dados em memória)
Contras:Maior acoplamento e dependência com o código servidor.

Integração em tempo de compilação

Com ela, a aplicação principal embarca os microfronts como bibliotecas versionadas. Essa estratégia garante o isolamento a nível de desenvolvimento, porém envolve o deploy e a execução de um único componente. Uma das vantagens desse modelo é que o download das bibliotecas principais pode ser realizado uma única vez, no primeiro download para o web browser. Alguns frameworks, como o Webpack, podem apoiar nessa implementação. Nessa abordagem, a junção dos fronts ocorre em tempo de compilação.

Prós:Produz um único pacote publicável de duplicação de dependências comuns (evita múltiplos downloads da mesma dependência).
Contras:Necessidade de recompilar toda a aplicação sempre que um microfrontend for modificado.Não possui isolamento em runtime (tanto no servidor, quanto no web browser). Apesar de permitir a decomposição em bases de código menores, é realizado acoplamento das partes a nível de compilação.

Integração em runtime com iFrames

Os temidos iFrames também proveem uma forma de implementar microfrontends. Com isso, o isolamento a nível de execução é total, permitindo, inclusive, utilizar frameworks javascript completamente diferentes em cada frontend. Claro que toda vantagem vem acompanhada de uma desvantagem.. então cuidado com o tempo de load dos frameworks e com a responsividade da aplicação.

Prós: Bom nível de isolamento, tanto a nível de servidor, quanto no browser (estilos e scripts); flexibilidade para desenvolver cada micro frontend em uma tecnologia distinta; aplicação contêiner que facilita o compartilhamento da lógica de navegação principal e, eventualmente, de aspectos de segurança e comunicação que são comuns a todos os fronts. Interessante para sistemas que possuem repetição da estrutura principal em todas as páginas.
Contras: Mudanças de layout entre páginas é limitada e pode gerar complexidade. Acoplamento maior entre o contêiner principal e dos microfrontends. Aspectos de segurança, como ataques XSS, devem ser tratados. A utilização de subdomínios do mesmo domínio principal para os microfrontends pode facilitar. Comunicação entre iFrames pode se tornar complexa e difícil de depurar.

Integração em runtime com Javascript

Se você não quiser trabalhar com iFrames, pode carregar o conteúdo HTML dos seus microfronts de forma dinâmica usando Javascript. Não haverá o mesmo isolamento a nível bibliotecas e CSS no web browser, porém, há maior liberdade para aplicar estilos e criar um design mais sofisticado. Nesse link, da página oficial do Martin Fowler, há um tutorial bem completo de como implementar essa estratégia usando ReactJS. E nesse aqui você pode conferir o projeto real executando!

Prós:Independência de build e deploy permite integrar os microfrontends de forma muito mais simples e flexível, pois não requer mecanismos complexos de troca de mensagens. Possibilita encapsular partes específicas como pacotes independentes, carregando-os uma única vez e reutilizando-os em diferentes microfrontends.
Contras: Não garante o máximo de isolamento em tempo de execução no browser, ou seja, pode haver interferência entre CSS e bibliotecas.

Integração em runtime com WebComponents

WebComponents são uma suíte de recursos que permite criar componentes customizados e reutilizáveis, de forma organizada e integrada ao DOM – o que facilita o desenvolvimento de microfrontends. Além disso, suportam a utilização de estilos internos ou externos ao componente – o que traz maior segurança à experiência do usuário. O resultado final é similar à integração em Runtime com Javascript, porém de uma maneira mais voltada à API de Web Components suportada pelo browser. Antes de utilizar qualquer recurso mais moderno, lembre-se de sempre observar a compatibilidade com os web browsers mais usados.

Prós: Independência de build e deploy. Permite integrar os microfrontends de forma muito mais simples e flexível, pois não requer mecanismos complexos de troca de mensagens. Possibilita encapsular partes específicas como pacotes independentes, carregando-os uma única vez e reutilizando-os em diferentes micro frontends. Utilização dos WebComponents como se fossem elementos nativos do HTML.
Contras: Não garante o máximo de isolamento em tempo de execução no browser, ou seja, pode haver interferência entre CSS e bibliotecas. A implementação está sujeita a restrições de compatibilidade dos web browsers.

Conclusão

Microfrontends trazem vantagens interessantes para aumentar o paralelismo no desenvolvimento e isolar e reduzir os riscos de publicação de releases em produção. No entanto, a técnica vem acompanhada de custo e complexidade maiores para o desenvolvimento. Há cenários em que não há paralelismo, ou ele é necessário apenas por um curto espaço de tempo. Nesses casos, cabe analisar se a complexidade que será adicionada ao projeto será compensada. De qualquer maneira, a técnica traz uma afinidade com a organização de times ágeis, uma vez que sua principal vantagem é dar maior autonomia para os times e reduzir as dependências entre eles.


Referências:

%d blogueiros gostam disto: