Micro frontends — uma abordagem prática
Salve o/
Cuidado! Um mundo de micro serviços pode transformar, com facilidade, o frontend no novo monolito. O que não é, necessariamente, algo ruim. Mas existe uma solução muito elegante: Micro frontends.
O que é
De uma forma bem simplista, micro frontend é uma forma de quebrar os grandes monólitos em pequenas partes, a fim de facilitar a manutenção das, agora pequenas, aplicações. Segue muito bem alinhado com o conceito de micro serviços.
Existem várias formas de se trabalhar com frontend (no final do artigo, deixo um link para uma publicação totalmente excelente do Martin Fowler):
- Server side
- Build-time (por exemplo, pacotes em um NPM privado)
- iFrames
- Runtime via javascript (veremos mais detalhes sobre este aqui)
- Runtime via WebComponents
Qual problema estamos resolvendo?
É muito comum encontrar aplicações com a stack legada (aquele angularJS maroto sendo mantido até hoje, por exemplo) ou algo novo que foi criado com aqueles deadlines absurdos que nem uma refatoração resolve… acabamos tendo que reescrever boa parte, senão tudo, num futuro próximo.
Quebrar esses monólitos em pequenas partes bonitas, brilhantes, performáticas e com a possibilidade de publicar novas funcionalidades com facilidade pode ser uma boa decisão. Assim, o legado vai inevitavelmente morrer de inanição. É muito mais simples uma transição mais lenta e controlada do que simplesmente abandonar o monolito enquanto a “refação” acontece. (Fica aqui uma promessa de um artigo falando sobre refatoração VS “refação”).
Na prática, o que acontece? Um primeiro (aventureiro e disruptivo) time consegue levar uma feature nova para produção sem a dor de cabeça e toda a carga emocional que vem com o legado. As coisas funcionam, todo mundo fica sabendo do case de sucesso e agora micro frontend passa a ser a arquitetura padrão!
Nesse mundo utópico e maravilhoso de micro frontends, podemos qualquer stack, controlar o update em casos de break changes nos frameworks (várias pequenas migrações, ao invés de uma única — quem é velho paia como eu, lembra muito bem do angular 2….), um deploy mais fluído e menos burocrático, pequenos repositórios contidos em pequenos contexto e com alta manutenibilidade (as matérias de graduação e pós graduação adoram essa palavra rs).
Quais problemas podemos criar?
Caso a arquitetura não seja muito bem planejada, a coisa pode sair de controle e os bundles dos micro frontends podem facilmente gerar um overhead.
Pensa comigo, cenário aqui com 10 aplicações:
1 usa angular 9
1 usa angular 7
3 usam vue 2
1 usa vue 3
4 usam react
Podemos facilmente perder a mão e carregar, por exemplo, o Vue 2 inteiro em duas aplicações distintas. E quatro vezes o React!
Mesmo utilizando o lazy load, isso pode facilmente se tornar um problemão!
Além disso, precisamos tomar cuidado com a autonomia dos times.
Um dos grandes benefícios de utilizar a arquitetura de micro frontends é justamente ter essas pequenas porções independentes.
Devemos tomar cuidado com time horizontais:
Créditos da imagem: https://martinfowler.com/articles/micro-frontends.html#AutonomousTeams
Hands on
Já dizia Linus Torvalds, talk is cheap.
Vamos para uma abordagem prática?
Em minhas idas e vindas, descobri essa ferramenta maravilhosa:
single-spa | single-spa — “A javascript router for front-end microservices”
Esse carinha nos dá excelentes ferramentas e toda a base necessária para criarmos e orquestrarmos de forma simples e eficiente o ecossistema de micro frontends.
Para começar, vamos conhecer o CLI.
Podemos instalar globalmente:
npm install — global create-single-spa
E então chamar o script rodando create-single-spa
Ou então, podemos utilizar o npx:
npx create-single-spa
Nesse exemplo, vou utilizar o react como padrão para todas as aplicações. Vou focar na criação dos mfes e na orquestração, mas todo o código fonte do projeto estará disponível no github.
Então, eles nos abre as opções disponíveis:
Começaremos pelo root config. Ele será o nosso orquestrador. Seguindo o passo a passo, responderemos as seguintes perguntas:
? Select type to generate single-spa root config
? Which package manager do you want to use? npm
? Will this project use Typescript? No
? Would you like to use single-spa Layout Engine No
? Organization name (can use letters, numbers, dash or underscore) neves
All done, agora basta entrar na pasta e rodar o projeto com npm start. Ele vai subir um servidor na porta 9000 por padrão, já carregando um mfe (micro frontend) por padrão:
Nesse primeiro momento, precisamos entender apenas o arquivo neves-root-config.js, onde nossa aplicação será registrada e configurada:
Já descobrimos de onde veio o micro frontend padrão. Pensando num exemplo de mundo real, vamos criar 3 mfes:
- Uma barra de navegação, que sempre será exibida (independente da rota atual).
- Uma aplicação com roteamento interno e lazy load
- Uma aplicação sem roteamento interno
Começando pela navbar:
create-single-spa navbar
? Select type to generate single-spa application / parcel
? Which framework do you want to use? react
? Which package manager do you want to use? npm
? Will this project use Typescript? No
? Organization name (can use letters, numbers, dash or underscore) neves
Vamos adicionar o react-router-dom para configurar os nossos links, um pouco de HTML e CSS. Feito isso, colocaremos o projeto para rodar na porta 8500:
cd navbar/
npm i react-router-dom
npm start — — port 8500
Detalhe! Ao terminar de criar a aplicação pra gente, o CLI nos retorna o seguinte:
Project setup complete!
Steps to test your React single-spa application:
1. Run ‘npm start — — port 8500’
2. Go to http://single-spa-playground.org/playground/instant-test?name=@neves/navbar&url=8500 to see it working!
Como estamos exportando apenas o necessário para a configuração do orquestrador, precisamos desse playground para ver o resultado do nosso trabalho.
Precisamos agora entender que, utilizando o CLI para criar o mfe, ele já trás pra gente todo o necessário para configurar e rodar a aplicação.
O resultado da função singleSpaReact já retorna pra gente os 3 callbacks necessários para o orquestrador:
- bootstrap
- mount
- unmount
O Root nada mais é do que um componente React e um pouco de css. Agora, precisamos configurar o Import Map para conhecer o novo MFE e então registrá-lo.
No importmap (index.ejs):
Registrando:
Atualizando o nosso http://localhost:9000/, nada muda na tela. Porém… temos um erro no console:
Por padrão, nosso MFE não vai colocar o React no bundle final. Ele entende que imports globais não são responsabilidade dele. Vamos então ajustar o nosso importmap para importar o react, o react-dom e o react-router-dom. Todas as nossas dependências externas ficam a cargo do orquestrador:
No index.ejs:
Agora sim! Voltando ao orquestrador, já podemos ver o Navbar renderizado com as 2 rotas que utilizaremos nesse artigo: Foo e Bar:
Antes de seguir, precisamos configurar o react-router-dom como uma dependência externa. Isso porque utilizaremos a mesma lib em outros mfes. Um ajuste simples no webpack.config.js:
Isso vai remover a lib do bundle, deixando a cargo do orquestrador esse import.
Vamos para o segundo MFe, com roteamento interno e lazy loading:
create-single-spa foo
? Select type to generate single-spa application / parcel
? Which framework do you want to use? react
? Which package manager do you want to use? npm
? Will this project use Typescript? No
? Organization name (can use letters, numbers, dash or underscore) neves
? Project name (can use letters, numbers, dash or underscore) foo
Assim como no navbar, vamos precisar do react-router-dom. Não se esqueça de configurar como dependência externa no webpack tb.
Aqui, mais do mesmo. Vai ser uma aplicação com três rotas, chamadas:
- home (raiz)
- point breaker
- strongest avenger
Cada rota exibindo a imagem do respectivo vingador. Como o foco aqui é a parte do micro frontend em si, o detalhe sobre o lazy load do react-router-dom está no fim do artigo para quem quiser saber mais.
Agora já é café pequeno, né? Incluir no import map e registrar o novo MFE. Detalhe que dessa vez vamos mudar a porta para 8501. Além disso, vamos remover aquele mfe que veio registrado por padrão.
Registrando agora somente as nossas aplicações:
O legal é que tanto o mfe Foo, quanto suas rotas internas só são carregadas quando chamadas:
http://localhost:9000/foo/point-breaker
E por último:
Então, mesmo com micro frontends, podemos utilizar esse recurso tão valioso do lazy loading.
Bom, agora vamos finalizar criando um microfrontend que será carregado na raiz, substituindo aquele mfe padrão que acabamos de remover.
Chamando o create-single-spa uma última vez para gerar o mfe:
create-single-spa bar
? Select type to generate single-spa application / parcel
? Which framework do you want to use? react
? Which package manager do you want to use? npm
? Will this project use Typescript? No
? Organization name (can use letters, numbers, dash or underscore) neves
? Project name (can use letters, numbers, dash or underscore) bar
Esse mfe vai ser bem simples, como um estalar de dedos. Uma única página que será carregada na raiz do nosso roteamento. O nosso ponto de entrada.
Mais uma vez, uma aplicação react super simples. Vamos registrar, ajustar o import map e pronto:
Missão dada, é missão cumprida:
Mas nem tudo são flores, e bugs acontecem. Queremos que o mfe bar só seja carregado na rota raiz. Mas, ao carregarmos a rota /foo ele também é renderizado, veja:
Isso acontece porque configuramos o mfe da seguinte forma:
Precisamos fazer um ajuste nessa propriedade para que o mfe só seja carregado caso a rota seja EXATA! Quando colocamos com essa sintaxe, sempre que a rota informada existir na url local, ele será renderizado. Vamos ajustar isso:
Uma alternativa é passar uma arrow function, que recebe como parâmetro o objeto global location. E agora sim, o mfe bar só é exibido na raiz, o mfe foo apenas quando a url contém /foo e o mfe navbar é exibido sempre, independente da url.
E por hoje é só!
Prometo trazer mais dicas sobre esse mundo maravilhoso dos micro frontends, pois ele pode ser ainda melhor! Principalmente a rotina de desenvolvimento e o deploy das aplicações, já deixando o spoiler do que vem por aí.
Vida longa e próspera!
Referências e para saber mais
https://developer.mozilla.org/en-US/docs/Web/Performance/Lazy_loading
https://martinfowler.com/articles/micro-frontends.html
https://single-spa.js.org/docs/create-single-spa/
https://single-spa.js.org/docs/videos
https://pt-br.reactjs.org/docs/code-splitting.html#reactlazy