Os pontos fortes e os benefícios dos micro frontends

Publicados: 2022-03-11

A arquitetura de micro-front-end é uma abordagem de design na qual um aplicativo de front-end é decomposto em “microaplicativos” individuais e semi-independentes, trabalhando livremente juntos. O conceito de microfront-end é vagamente inspirado e batizado com o nome de microsserviços.

Os benefícios do padrão micro-frontend incluem:

  1. As arquiteturas de microfrontend podem ser mais simples e, portanto, mais fáceis de raciocinar e gerenciar.
  2. Equipes de desenvolvimento independentes podem colaborar em um aplicativo front-end com mais facilidade.
  3. Eles podem fornecer um meio para migrar de um aplicativo “antigo” com um aplicativo “novo” sendo executado lado a lado com ele.

Embora os micro frontends tenham recebido muita atenção ultimamente, ainda não há uma única implementação dominante e nenhuma estrutura clara de "melhor" microfrontend. Na verdade, há uma variedade de abordagens, dependendo dos objetivos e requisitos. Veja a bibliografia para algumas das implementações mais conhecidas.

Neste artigo, pularemos muito da teoria dos micro frontends. Aqui está o que não abordaremos:

  • “Fatiar” um aplicativo em microaplicativos
  • Problemas de implantação, incluindo como os micro frontends se encaixam em um modelo de CI/CD
  • Teste
  • Se os microaplicativos devem estar alinhados um a um com os microsserviços no back-end
  • Críticas ao conceito de micro-frontend
  • A diferença entre micro frontends e uma arquitetura simples de componentes antigos

Em vez disso, apresentaremos um tutorial de micro-frontend com foco em uma implementação concreta, destacando as questões importantes na arquitetura de micro-frontend e suas possíveis soluções.

Nossa implementação é chamada Yumcha. O significado literal de “yum cha” em cantonês é “beber chá”, mas seu significado cotidiano é “sair para dim sum”. A ideia aqui é que os microaplicativos individuais dentro de um macroaplicativo (como chamaremos o aplicativo composto de nível superior) são análogos às várias cestas de porções pequenas trazidas em um almoço dim sum.

Ilustração de visão geral de um aplicativo baseado em micro frontend de exemplo, conforme descrito acima.

Às vezes, nos referimos ao Yumcha como um “framework micro-frontend”. No mundo de hoje, o termo “framework” geralmente é usado para se referir a Angular, React, Vue.js ou outras superestruturas semelhantes para aplicativos da web. Não estamos falando de uma estrutura nesse sentido. Chamamos o Yumcha de framework apenas por conveniência: na verdade, é mais um conjunto de ferramentas e algumas camadas finas para criar aplicativos baseados em micro frontend.

Primeiros passos do tutorial de microfrontend: marcação para um aplicativo composto

Vamos mergulhar pensando em como podemos definir um macroaplicativo e os microaplicativos que o compõem. A marcação sempre esteve no coração da web. Nosso macroapp será, portanto, especificado por nada mais complicado do que esta marcação:

 <html> <head> <script src="/yumcha.js"></script> </head> <body> <h1>Hello, micro-frontend app.</h1> <!-- HERE ARE THE MICROAPPS! --> <yumcha-portal name="microapp1" src="https://microapp1.example.com"></yumcha-portal> <yumcha-portal name="microapp2" src="https://microapp2.example.com"></yumcha-portal> </body> </html>

Definir nosso macroapp usando marcação nos dá acesso total ao poder do HTML e CSS para organizar e gerenciar nossos microapps. Por exemplo, um microaplicativo pode ficar em cima de outro, ou ao seu lado, ou estar no canto da página, ou estar em um painel de um acordeão, ou permanecer oculto até que algo aconteça, ou ficar em segundo plano permanentemente .

Chamamos o elemento personalizado usado para <yumcha-portal> porque “portal” é um termo promissor para microaplicativos usados ​​na proposta do portal, uma tentativa inicial de definir um elemento HTML padrão para uso em micro frontends.

Implementando o elemento personalizado <yumcha-portal>

Como devemos implementar <yumcha-portal> ? Já que é um elemento customizado, como componente web, claro! Podemos escolher entre vários concorrentes fortes para escrever e compilar componentes web micro-frontend; aqui usaremos LitElement, a última iteração do Projeto Polymer. LitElement suporta açúcar sintático baseado em TypeScript, que lida com a maioria do clichê de elementos personalizados para nós. Para disponibilizar <yumcha-portal> em nossa página, temos que incluir o código relevante como um <script> , como fizemos acima.

Mas o que <yumcha-portal> realmente faz? Uma primeira aproximação seria apenas criar um iframe com a fonte especificada:

 render() { return html`<iframe src=${this.src}></iframe>`; }

…onde render é o gancho de renderização padrão do LitElement, usando seu literal de modelo com tag html . Essa funcionalidade mínima pode ser quase suficiente para alguns casos de uso triviais.

Incorporando Microapps em iframe s

iframe s são o elemento HTML que todo mundo adora odiar, mas na verdade eles fornecem um comportamento de sandbox extremamente útil e sólido. No entanto, ainda há uma longa lista de problemas a serem observados ao usar iframe s, com impacto potencial no comportamento e na funcionalidade do nosso aplicativo:

  • Primeiro, os iframe têm peculiaridades bem conhecidas em termos de tamanho e disposição.
  • É claro que o CSS será completamente isolado do iframe , para melhor ou para pior.
  • O botão "voltar" do navegador funcionará razoavelmente bem, embora o status de navegação atual do iframe não seja refletido no URL da página , portanto, não poderíamos recortar e colar URLs para chegar ao mesmo estado do aplicativo composto, nem link direto para eles.
  • A comunicação externa com o iframe , dependendo de nossa configuração CORS, pode precisar passar pelo protocolo postMessage .
  • Terão de ser tomadas providências para autenticação através dos limites do iframe .
  • Alguns leitores de tela podem tropeçar no limite do iframe ou precisar que o iframe tenha um título que possa anunciar ao usuário.

Alguns desses problemas podem ser evitados ou atenuados não usando iframe s, uma alternativa que discutiremos mais adiante neste artigo.

No lado positivo, o iframe terá sua própria e independente Content-Security-Policy (CSP). Além disso, se o microapp para o qual o iframe aponta usar um service worker ou implementar a renderização do lado do servidor, tudo funcionará conforme o esperado. Também podemos especificar várias opções de sandboxing para o iframe para limitar seus recursos, como navegar até o quadro superior.

Alguns navegadores enviaram ou estão planejando enviar um atributo loading=lazy para iframe s, que adia o carregamento de iframe s abaixo da dobra até que o usuário role para perto deles, mas isso não fornece o controle refinado do carregamento lento que nós queremos.

O verdadeiro problema com iframe s é que o conteúdo do iframe levará várias solicitações de rede para ser recuperada. O index.html de nível superior é recebido, seus scripts são carregados e seu HTML é analisado, mas o navegador deve iniciar outra solicitação para o HTML do iframe , esperar para recebê-lo, analisar e carregar seus scripts e renderizar o conteúdo do iframe . Em muitos casos, o JavaScript do iframe ainda teria que girar, fazer suas próprias chamadas de API e mostrar dados significativos somente depois que essas chamadas de API retornassem e os dados fossem processados ​​para visualização.

Isso provavelmente resultará em atrasos indesejáveis ​​e artefatos de renderização, especialmente quando vários microaplicativos estiverem envolvidos. Se o aplicativo do iframe implementar SSR, isso ajudará, mas ainda não evitará a necessidade de viagens de ida e volta adicionais.

Portanto, um dos principais desafios que enfrentamos ao projetar nossa implementação de portal é como lidar com esse problema de ida e volta. Nosso objetivo é que uma única solicitação de rede derrube a página inteira com todos os seus microaplicativos, incluindo qualquer conteúdo que cada um deles possa pré-preencher. A solução para este problema está no servidor Yumcha.

O servidor Yumcha

Um elemento-chave da solução de microfrontend apresentada aqui é configurar um servidor dedicado para lidar com a composição de microaplicativos. Esse servidor faz proxy de solicitações para os servidores em que cada microaplicativo está hospedado. Concedido, exigirá algum esforço para configurar e gerenciar este servidor. Algumas abordagens de microfront-end (por exemplo, spa único) tentam dispensar a necessidade de tais configurações de servidor especiais, em nome da facilidade de implantação e configuração.

No entanto, o custo de criação dessa proxy reversa é mais do que compensado pelos benefícios que obtemos; na verdade, existem comportamentos importantes de aplicativos baseados em micro frontend que simplesmente não podemos alcançar sem ele. Existem muitas alternativas comerciais e gratuitas para configurar esse proxy reverso.

O proxy reverso, além de rotear solicitações de microaplicativos para o servidor apropriado, também roteia solicitações de macroaplicativos para um servidor de macroaplicativos. Esse servidor prepara o HTML para o aplicativo composto de uma maneira especial. Ao receber uma solicitação de index.html do navegador por meio do servidor proxy em uma URL como http://macroapp.example.com , ele recupera o index.html e o submete a uma transformação simples, mas crucial, antes de retornar isto.

Especificamente, o HTML é analisado para <yumcha-portal> , o que pode ser feito facilmente com um dos analisadores HTML competentes disponíveis no ecossistema Node.js. Usando o atributo src para <yumcha-portal> , o servidor que executa o microapp é contatado e seu index.html é recuperado—incluindo o conteúdo renderizado do lado do servidor, se houver. O resultado é inserido na resposta HTML como uma tag <script> ou <template> , para não ser executado pelo navegador.

Ilustração da arquitetura do servidor do Yumcha. O navegador se comunica com o proxy reverso, que por sua vez se comunica com o macroapp e cada um dos microapps. A etapa macroapp transforma e preenche o arquivo index.html principal do aplicativo.

As vantagens dessa configuração incluem, em primeiro lugar, que na primeira solicitação do index.html para a página composta, o servidor pode recuperar as páginas individuais dos servidores de microaplicativos individuais em sua totalidade, incluindo conteúdo renderizado por SSR, se qualquer — e entregar uma página única e completa ao navegador, incluindo o conteúdo que pode ser usado para preencher o iframe sem viagens de ida e volta adicionais ao servidor (usando o atributo srcdoc subutilizado). O servidor proxy também garante que todos os detalhes de onde os microaplicativos estão sendo servidos sejam escondidos de olhares indiscretos. Por fim, simplifica os problemas de CORS, uma vez que as requisições das aplicações são todas feitas para a mesma origem.

De volta ao cliente, a <yumcha-portal> é instanciada e encontra o conteúdo onde foi colocado no documento de resposta pelo servidor, e no momento apropriado renderiza o iframe e atribui o conteúdo ao seu atributo srcdoc . Se não estivermos usando iframe s (veja abaixo), o conteúdo correspondente a essa <yumcha-portal> será inserido no shadow DOM do elemento personalizado, se estivermos usando isso, ou diretamente embutido no documento.

Neste ponto, já temos um aplicativo baseado em micro frontend parcialmente funcional.

Esta é apenas a ponta do iceberg em termos de funcionalidades interessantes para o servidor Yumcha. Por exemplo, gostaríamos de adicionar recursos para controlar como as respostas de erro HTTP dos servidores de microaplicativos são tratadas ou como lidar com microaplicativos que respondem muito lentamente - não queremos esperar para sempre para servir a página se um microaplicativo não for respondendo! Esses e outros temas deixaremos para outro post.

A lógica de transformação do Yumcha macroapp index.html pode ser facilmente implementada em uma forma de função lambda sem servidor ou como middleware para estruturas de servidor, como Express ou Koa.

Controle de Microapp baseado em Stub

Voltando ao lado do cliente, há outro aspecto de como implementamos microaplicativos que é importante para eficiência, carregamento lento e renderização sem instabilidade. Poderíamos gerar a tag iframe para cada microaplicativo, seja com um atributo src - que faz outra solicitação de rede - ou com o atributo srcdoc preenchido com o conteúdo preenchido para nós pelo servidor. Mas em ambos os casos, o código nesse iframe será iniciado imediatamente, incluindo o carregamento de todos os seus scripts e tags de link, bootstrapping e quaisquer chamadas iniciais de API e processamento de dados relacionados, mesmo que o usuário nunca acesse o microapp em questão.

Nossa solução para esse problema é inicialmente representar microaplicativos na página como pequenos stubs inativados, que podem então ser ativados. A ativação pode ser conduzida pela visualização da região do microapp, usando a API IntersectionObserver subutilizada, ou mais comumente por pré-notificações enviadas de fora. Claro, também podemos especificar que o microapp seja ativado imediatamente.

De qualquer forma, quando e somente quando o microapp é ativado, o iframe é realmente renderizado e seu código carregado e executado. Em termos de nossa implementação usando LitElement, e assumindo que o status de ativação é representado por uma variável de instância activated , teríamos algo como:

 render() { if (!this.activated) return html`{this.placeholder}`; else return html` <iframe srcdoc="${this.content}" @load="${this.markLoaded}"></iframe>`; }

Comunicação entre microaplicativos

Embora os microaplicativos que compõem um macroaplicativo sejam, por definição, fracamente acoplados, eles ainda precisam ser capazes de se comunicar entre si. Por exemplo, um microaplicativo de navegação precisaria enviar uma notificação de que algum outro microaplicativo recém-selecionado pelo usuário deve ser ativado, e o aplicativo a ser ativado precisa receber essas notificações.

De acordo com nossa mentalidade minimalista, queremos evitar a introdução de muitas máquinas de transmissão de mensagens. Em vez disso, no espírito dos componentes da Web, usaremos eventos DOM. Nós fornecemos uma API de broadcast trivial que pré-notifica todos os stubs de um evento iminente, espera por qualquer um que tenha solicitado ser ativado para que esse tipo de evento seja ativado e, em seguida, despacha o evento para o documento, no qual qualquer microaplicativo pode ouvir. isto. Dado que todos os nossos iframe são de mesma origem, podemos ir do iframe para a página e vice-versa para encontrar elementos contra os quais disparar eventos.

Roteamento

Atualmente, todos esperamos que a barra de URL em SPAs represente o estado de exibição do aplicativo, para que possamos recortar, colar, enviar por e-mail, enviar texto e vincular a ela para ir diretamente para uma página dentro do aplicativo. Em um aplicativo de microfront-end, no entanto, o estado do aplicativo é, na verdade, uma combinação de estados, um para cada microaplicativo. Como devemos representar e controlar isso?

A solução é codificar o estado de cada microaplicativo em um único URL composto e usar um pequeno roteador de macroaplicativo que saiba como juntar esse URL composto e separá-lo. Infelizmente, isso requer uma lógica específica do Yumcha em cada microapp: receber mensagens do roteador do macroapp e atualizar o estado do microapp e, inversamente, avisar o roteador do macroapp sobre alterações nesse estado para que a URL composta possa ser atualizada. Por exemplo, pode-se imaginar um YumchaLocationStrategy para Angular ou um elemento <YumchaRouter> para React.

Um URL composto que representa um estado de macroapp. Sua string de consulta é decodificada em duas strings de consulta separadas (duplamente codificadas) que devem ser passadas para os microaplicativos cujos ids são especificados como suas chaves.

O Caso Não- iframe

Como mencionado acima, hospedar microaplicativos em iframe s tem algumas desvantagens. Existem duas alternativas: incluí-los diretamente no HTML da página ou colocá-los no shadow DOM. Ambas as alternativas refletem um pouco os prós e os contras dos iframe , mas às vezes de maneiras diferentes.

Por exemplo, políticas de CSP de microaplicativos individuais teriam que ser mescladas de alguma forma. Tecnologias assistivas, como leitores de tela, devem funcionar melhor do que com iframe s, supondo que suportem o shadow DOM (o que nem todos suportam ainda). Deve ser simples organizar o registro de service workers de um microapp usando o conceito de service worker de "escopo", embora o aplicativo tenha que garantir que seu service worker seja registrado com o nome do aplicativo, não "/" . Nenhum dos problemas de layout associados ao iframe se aplica aos métodos DOM inline ou shadow.

No entanto, os aplicativos criados usando estruturas como Angular e React provavelmente serão infelizes vivendo inline ou no shadow DOM. Para esses, provavelmente vamos querer usar iframe s.

Os métodos inline e shadow DOM diferem quando se trata de CSS. CSS será encapsulado de forma limpa no shadow DOM. Se por algum motivo quiséssemos compartilhar CSS externo com o shadow DOM, teríamos que usar folhas de estilo construtíveis ou algo semelhante. Com microapps embutidos, todo CSS seria compartilhado em toda a página.


No final, implementar a lógica para microapps DOM inline e shadow no <yumcha-portal> é simples. Recuperamos o conteúdo de um determinado microaplicativo de onde ele foi inserido na página pela lógica do servidor como um elemento HTML <template> , clonamos e anexamos ao que LitElement chama de renderRoot , que normalmente é o shadow DOM do elemento, mas pode também ser definido para o próprio elemento ( this ) para o caso inline (non-shadow DOM).

Mas espere! O conteúdo fornecido pelo servidor do microapp é uma página HTML inteira. Não podemos inserir a página HTML do microapp, completa com as tags html , head e body , no meio da página do macroapp, podemos?

Resolvemos esse problema aproveitando uma peculiaridade da tag de template em que o conteúdo do microapp recuperado do servidor do microapp é encapsulado. Acontece que quando os navegadores modernos encontram uma tag de template , embora não a "executem", eles a analisam e, ao fazê-lo, removem conteúdo inválido, como as tags <html> , <head> e <body> , preservando seu conteúdo interno. Assim, as tags <script> e <link> no <head> , bem como o conteúdo do <body> , são preservados. Isso é exatamente o que queremos para fins de inserção de conteúdo de microaplicativos em nossa página.

Arquitetura micro-frontend: o diabo está nos detalhes

Micro frontends se enraizarão no ecossistema de webapps se (a) eles se tornarem uma abordagem de arquitetura melhor e (b) pudermos descobrir como implementá-los de maneira que satisfaça os inúmeros requisitos práticos da web de hoje.

Em termos da primeira pergunta, ninguém afirma que micro frontends são a arquitetura certa para todos os casos de uso. Em particular, haveria poucas razões para o desenvolvimento greenfield por uma única equipe adotar micro frontends. Vou deixar a questão de quais tipos de aplicativos em quais tipos de contextos podem se beneficiar mais de um padrão de microfrontend para outros comentaristas.

Em termos de implementação e viabilidade, vimos que há muitos detalhes com os quais se preocupar, incluindo vários nem sequer mencionados neste artigo, principalmente autenticação e segurança, duplicação de código e SEO. No entanto, espero que este artigo estabeleça uma abordagem básica de implementação para micro frontends que, com mais refinamento, possa atender aos requisitos do mundo real.

Bibliografia

  • Micro Front ends - Fazendo em estilo angular - Parte 1
  • Micro Front Ends - Fazendo isso no estilo angular - Parte 2
  • Evoluindo um aplicativo AngularJS usando microfrontends
  • Microfrontends
  • Microsserviços de interface do usuário — revertendo o antipadrão (micro frontends)
  • Microsserviços de interface do usuário – um antipadrão?
  • A criação de páginas usando Micro-Frontends adota uma abordagem semelhante ao Yumcha de proxy reverso e SSIs, o que eu recomendo.
  • Recursos de microfrontends
  • Pódio
  • Eu não entendo Micro-Frontends. Esta é uma boa visão geral dos tipos de arquiteturas de micro-frontend e casos de uso.
  • Microfrontends sem servidor usando Vue.js, AWS Lambda e Hypernova
  • Micro Frontends: Uma visão geral excelente e abrangente.