Sitemap

Micro Front Ends e Angular — Evoluindo um projeto por 3 anos

8 min readFeb 8, 2021

--

Há 3 anos tenho trabalhado em um projeto onde acho interessante, agora que estamos chegando perto da solução final, replicar tudo que passamos de desafios e problemas do angular trabalhar com modelos de micro front ends da maneira que desejamos ter.

Fase 1- Ou como pequenas decisões podem gerar impactos gigantes por anos (2018/2019)

Todo o projeto visava termos um sistema de micro front ends realmente independente, onde pudéssemos deixar todas as squads trabalhando sem impacto uma nas outras, porém, sempre seguindo um padrão de reaproveitamento de código. Isso foi decidido no final de 2018.

Posso resumir o grande objetivo deste projeto ser pensado em micro frontends ou microapps, sendo:

Desejo de ter uma única base tecnológica que sirva vários canais, dando independência aos times de negocio de criarem novas funcionalidades, sem pontos únicos de falha e escalabilidade tecnológica e de novos negócios

Nesta época, ainda estávamos no Angular 7, que ainda aceitava bibliotecas e padrões desde o Angular 4.

Acabou-se fechando no desenho abaixo de arquitetura da primeira versão:

Press enter or click to view image in full size
Primeira versão de arquitetura angular com micro applications

Micro Front Ends (Modulos de features): todos desenvolvidos como angular 7 libraries, a fim de deixar seu peso o menor possível.

Libs internas: a fim de criar um padrão, varias bibliotecas foram criadas, com destaque a um design system do zero, uma lib de chamadas de api, e uma lib de controle de dados em localstorage.

Core

Por uma decisão arquitetural, o nosso core, hoje chamado em vários padrões de app shell, continha todos os pontos críticos e gerais do projeto: paginas de erro, padrões de design gerados via sass, todo o fluxo de autenticação, e alguns itens que vamos detalhar agora:

Environment: por seguirmos padrões devops, a idéia sempre foi que tivéssemos um pacote imutável entre ambientes de desenvolvimento, homologação, staging e produção. Para isso, criamos, lendo as variáveis de ambiente, um arquivo que gerava um arquivo config.js com as urls de chamadas de api, chaves, etc.

Consumo de Micro Frontends: Como ainda estava se começando a discutir de verdade micro frontends em angular, criamos um serviço baseado basicamente em SystemJS e na opção do angular compileModuleAndAllComponentsAsync, que permitia que importássemos os módulos em nosso core, consumidos de outra instancia de container.

Rotas: Para habilitarmos várias funcionalidades internas de nosso aplicativo (navbar, autenticação, feature flags) criamos dentro de nosso assets um arquivo json que nosso roteamento interpretava e descobria os outros pods, além de gerar regras nginx via proxy_pass para localização. O mesmo shell que criava o config.js, criava nosso arquivo de configuração do nginx.

Service Worker: cacheávamos nosso shell e nossas chamadas, além de permitir a instalação PWA do sistema.

App “híbrida” — a idéia inicial foi tirarmos tudo de aplicativos nativos e usarmos somente PWA. para faze-lo em fases, utilizamos uma versão hibrida, mas não a comumente citada de buildar por Cordova na época, mas sim de consumir o site via “webview”.

Go Live — Vitórias e Derrotas

A equipe responsável por criar toda a primeira versão do sistema lançou no fim de 2019 essa primeira versão. Pelo uso de feature flags, conseguimos balancear os primeiros usuários, analisar os problemas e irmos corrigindo.

Por termos somente uma equipe trabalhando em todos os modulos, não havia dificuldade de go live, de sincronia de conhecimento, conflitos de versionamento, tudo subindo no mesmo cloud service, etc.

Os principais problemas técnicos que ressalto da época foram:

Convivência nativo e PWA: o cache do service worker não trabalhava bem com o da webview do aplicativo nativo, assim como tínhamos 2 sistemas convivendo recebendo melhorias e correções de bugs simultaneamente.

Atualização constante de libs: Pegando como exemplo a lib de chamadas de api, ela foi desenvolvida de uma maneira que tínhamos que atualiza-la a cada nova chamada de api, o que fazia termos que subirmos muitas vezes o nosso core.

Conclusão:

Para uma equipe trabalhando unida em um ambiente inicial de projeto, utilizando Angular7 e o que tinhamos na época, era uma boa solução.

Mas, como veremos na parte 2, os problemas de escala dentro de uma grande empresa, e a idéia original, geraram um pesadelo de arquitetura a ser resolvido.

Fase 2 — Dividir para Conquistar — 2020 no inicio da pandemia

Logo após o lançamento do projeto piloto, começou-se o grande objetivo do projeto, de dar independência as equipes. Várias equipes, de consultorias diferentes, criando features por áreas de negócio.

De cara, um problema tradicional: transferência de conhecimento. Na minha opinião, este modelo de várias consultorias isoladas trabalhando junto não funciona. Sempre há uma certa rixa pelo tamanho do contrato, de segurar conhecimento, ou até de coisas mais simples como a dificuldade de fazer uma transferência formal de conhecimento sem terem detalhado em contrato, um mal de aplicação errônea de métodos ágeis.

Mas, voltando ao nosso foco de problemas arquiteturais de front end, encaramos o seguinte:

Dependência extrema do “Core”: Algo que pessoalmente brinco como um dick typing de monólito:

Se todo mundo tem que usar um sistema para testar suas aplicações

Se todas as chamadas passam pelo sistema

Se qualquer outra alteração só irá funcionar se este sistema for atualizado

Isso é um monólito! Um monólito disfarçado de orquestrador. vejam os problemas técnicos encontrados:

Dependência de atualização do arquivo modules.json: Legal, temos um arquivo que permite incluir outros módulos dinamicamente. Só que este arquivo está dentro do assets do Core

Necessidade de sincronia de versões: Se uma library esta no Core em uma versão, obrigatoriamente todas tem que usa-la. Se há alguma diferença, a interpretação do compileModuleAndAllComponentsAsync não será correta e quebrará!

Imagens de CSS dependendo de subidas do Core: por não utilizarmos uma CDN, e sim o assets dentro do Core, mesmo com caches na frente e via Service Worker, qualquer deploy depende dele.

Distribuição em Clouds diferentes: Se tornou necessário uma mudança de hosting para alguns serviços. E a utilização de rotas no nginx do Core dificultava a chamada de outros serviços por regras de firewall e outros pontos de infra.

Get Paulo Marinho’s stories in your inbox

Join Medium for free to get updates from this writer.

Conflitos de Config Maps/Environment: com as equipes distribuídas, nada evitava de termos módulos, chamadas de API com o mesmo nome, ou uma equipe alterando uma API e não comunicando a outra.

Gerenciamento de desenvolvimento: Na situação de Angular7, nada impedia para problemas específicos utilizarem bibliotecas npm Angular 4+. Além disso, a utilização de LocalStorage sem passar pela library criada fez com que se guardasse dados não necessários para comunicação entre módulos, inclusive mesmos dados duplicados, triplicados, com nomes de chave diferente.
E nosso design system começou a ter varias versões similares de um mesmo componente, que não seriam necessários, como app-button, button-pwa e ng-button, todos fazendo quase o mesmo.

Por questões de prazo e time, fizemos uma pequena evolução:

Press enter or click to view image in full size
Solução 2020 inicial

A saída do modules.json permitiu a subida de módulos sem subidas do core, assim como a criação de uma DMZ na frente rotear módulos entre hostings.

Outra melhoria foi a independência da library de chamada de apis para não ter que ser regerada com novas chamadas de api.

Porém, ao chegarmos em um ano do produto em produção, chegamos a outras necessidades:

Testes E2E — no momento necessariamente tendo que passar pelo core, sem independência

Auditoria — Dados salvos em LocalStorage sem criptografia nunca são uma boa pratica

OutDated — Muitas soluções lançadas no ultimo ano e meio somente funcionariam em uma nova arquitetura em Angular 11+, pelo uso de Module Federation, novas funcionalidades como import de módulos diretamente.

Revisão de Design System — limpeza e reorganização de itens basicos e desatualizados

Disseminação de conhecimento entre consultorias

Fase 3 — Como fazer uma mudança radical sem ser um Big Bang

Desde o inicio, temos módulos utilizando bibliotecas Angular 4+ 8-. E em um sistema em produção com milhões de usuários diários, não há como fazer uma mudança “Big Bang”.

A fase 3, ainda em andamento, se baseia em outras subfase:

1 — Possibilidade de migração gradual:

Preparamos nosso core para acessar nossos micro frontends totalmente independentes. Para isso, preparamos uma migração de libraries ng7 para applications ng11+. Os configmaps serão por aplicação, sempre quando possivel utilizando url canonicas, somente mudando o subdomínio.

Estamos mantendo nossas libs em versões 7 e 11, com migrações de convivência durante essa etapa.

Externalizamos nosso design system no assets apartado, garantindo a importação do SASS compilado em cada componente, sem ser uma biblioteca somente angular.

Preparamos uma lib-dados para entenderem os dados ofuscados em SessionStorage e LocalStorage, com um fallback de não haver ofuscação.

Com isso, chegaremos ao ponto de todas nossas features (mais de 40 projetos), estarem sendo servidas em Angular11, como aplicação, com suas dependências isoladas, com testes E2E isolados, seguindo os padrões de design definidos.

Temos ciencia do aumento de peso de cada modulo, que deverão ir de 300K a 6 a 10MB por terem novamente o angular empacotado.

2-Core as a Shell

Tendo todas as dependências migradas, podemos finalmente migrar nosso Core para se comportar com um shell.

Após o isolamento dos módulos, voltaremos a consumi-los, mas não como compileModuleAndAllComponentsAsync, e sim com o import * from ‘URL’ atualmente usado, seguindo muito do proposto pelos AngularArchitects.

Sem as dependências fixas de libraries, podemos usar o core como um shell real, somente definindo os padrões de design e com realmente as páginas de fallback, como 404, conexão interrompida, etc.

E como um gerenciador do estado da aplicação por completo, tirando a dependência de uma lib-dados quando desejado, passando como props o estado e recebendo de volta a cada mudança.

3-Module Federation

Ao começarmos a utilizarmos module federation, possibilitamos a volta de reaproveitamento de libraries como as básicas do angular, mas somente se a feature desejar. Caso haja uma decisão da equipe, eles podem utilizar outro tipo de lib/framework, versões diferentes, e tudo mais.

Como começamos a utilizar projetos que não sejam em angular, também possibilitamos isto, assim como a utilização mais simples do que em Angular7 de web components e soluções similares.

Press enter or click to view image in full size

Futuro

Em nossa solução, achamos que estamos no melhor caminho possível, vendo que nosso stack ainda é angular e temos mais pessoas experientes nessa framework.

Como nosso Design Ops está mudando para web Components, e toda nossa suíte idem, podemos caminhar para outro tipo de solução, mas o principal é que chegamos em nossos objetivos e não temos porque dar passos para trás:

Independência de equipes e de frameworks

Reaproveitamento de código

Testes e Segurança

Conhecimento (via compodoc e mistura de consultorias em equipes)

Entrega de valor

Produtividade

--

--

Paulo Marinho
Paulo Marinho

Written by Paulo Marinho

Manager, Developer, Writer, Musician and out of the box