Assine para receber notificações de novos posts:

Adoção gradual de micro front-ends com o Cloudflare Workers

2022-11-17

10 min. de leitura
Este post também está disponível em English, 繁體中文, Français, Deutsch, 日本語, Español e 简体中文.

Traga os benefícios de micro front-ends para aplicativos web legados

Incremental adoption of micro-frontends with Cloudflare Workers

Recentemente, escrevemos sobre uma nova arquitetura de fragmentos para criar aplicativos web que é rápida, econômica e escalável para os maiores projetos, ao mesmo tempo em que permite um ciclo de iteração rápido. A abordagem usa o Cloudflare Workers colaborativo para renderizar e transmitir micro front-ends em um aplicativo interativo mais rápido do que as abordagens tradicionais do lado do cliente, levando a uma melhor experiência do usuário e pontuações de SEO.

Essa abordagem é ótima se você estiver iniciando um novo projeto ou tiver condições de reescrever seu aplicativo atual do zero. Mas, na realidade, a maioria dos projetos é muito grande para ser reconstruída do zero e pode adotar mudanças arquitetônicas apenas de forma incremental.

Neste post, propomos uma maneira de substituir apenas partes selecionadas de um aplicativo legado renderizado do lado do cliente por fragmentos renderizados do lado do servidor. O resultado é um aplicativo em que as visualizações mais importantes ficam interativas antecipadamente, pode ser desenvolvido de forma independente e recebe todos os benefícios da abordagem de micro -front-end, evitando grandes reescritas da base de código legada. Essa abordagem é independente de estrutura; neste post demonstramos fragmentos construídos com React, Qwik e SolidJS.

O problema de aplicativos com grandes front-ends

Muitos aplicativos com grandes front-ends desenvolvidos hoje falham em oferecer uma boa experiência do usuário. Isso geralmente é causado por arquiteturas que exigem que grandes quantidades de JavaScript sejam baixadas, analisadas e executadas antes que os usuários possam interagir com o aplicativo. Apesar dos esforços para adiar o código JavaScript não crítico por meio de carregamento lento e o uso de renderização do lado do servidor, esses aplicativos grandes ainda demoram muito para se tornarem interativos e responderem às entradas do usuário.

Além disso, aplicativos monolíticos grandes podem ser complexos para desenvolver e implantar. Várias equipes podem colaborar em uma única base de código e o esforço para coordenar o teste e a implantação do projeto dificulta o desenvolvimento, a implantação e a iteração de recursos individuais.

Conforme descrito em nosso post anterior, os micro front-ends desenvolvidos pelo Cloudflare Workers podem resolver esses problemas, mas converter um aplicativo monolítico em uma arquitetura de micro front-end pode ser difícil e caro. Pode levar meses, ou mesmo anos, de tempo de engenharia antes que quaisquer benefícios sejam percebidos pelos usuários ou desenvolvedores.

O que precisamos é de uma abordagem em que um projeto possa adotar gradualmente micro front-ends nas partes mais impactantes do aplicativo de forma incremental, sem a necessidade de reescrever todo o aplicativo de uma só vez.

Os fragmentos são a salvação

O objetivo de uma arquitetura baseada em fragmentos é diminuir significativamente a latência de carregamento e interação para grandes aplicativos web (conforme medido por meio do Core Web Vitals), dividindo o aplicativo em micro front-ends que podem ser rapidamente renderizados (e armazenados em cache) no Cloudflare Workers. O desafio é como integrar um fragmento de micro front-end em um aplicativo legado renderizado do lado do cliente com custo mínimo para o projeto original.

A técnica que propomos nos permite converter as partes mais valiosas da interface do usuário de um aplicativo legado, isoladamente do restante do aplicativo.

Acontece que, em muitos aplicativos, as partes mais valiosas da interface do usuário geralmente estão aninhadas em um “shell” do aplicativo que fornece cabeçalho, rodapé e elementos de navegação. Exemplos disso incluem um formulário de login, um painel de detalhes do produto em um aplicativo de comércio eletrônico, a caixa de entrada em um cliente de e-mail, etc.

Vamos pegar um formulário de login como exemplo. Se nosso aplicativo levar vários segundos para exibir o formulário de login, os usuários ficam com receio de fazer login e podemos perdê-los. No entanto, podemos converter o formulário de login em um fragmento renderizado do lado do servidor, que é exibido e fica interativo imediatamente, enquanto o restante do aplicativo legado é inicializado em segundo plano. Como o fragmento fica interativo desde o início, o usuário pode até enviar suas credenciais antes que o aplicativo legado seja iniciado e o restante da página renderizado.

Animação mostrando o formulário de login sendo disponibilizado antes do aplicativo principal

Animation showing the login form being available before the main application

Essa abordagem permite que as equipes de engenharia forneçam melhorias valiosas aos usuários em apenas uma fração do tempo e do custo de engenharia em comparação com as abordagens tradicionais, que sacrificam as melhorias na experiência do usuário ou exigem uma reescrita longa e de alto risco de todo o aplicativo. Ela permite que as equipes com aplicativos monolíticos de página única adotem uma arquitetura de micro front-end de forma incremental, direcionem as melhorias para as partes mais valiosas do aplicativo e, assim, antecipem o retorno do investimento.

Um desafio interessante na extração de partes da IU em fragmentos renderizados do lado do servidor é que, uma vez exibidos no navegador, queremos que o aplicativo legado e os fragmentos pareçam um único aplicativo. Os fragmentos devem estar perfeitamente incorporados no shell do aplicativo legado, mantendo o aplicativo acessível formando corretamente a hierarquia DOM, mas também queremos que os fragmentos renderizados do lado do servidor sejam exibidos e se tornem interativos o mais rápido possível, antes mesmo que o shell do aplicativo legado do lado do cliente renderizado exista. Como podemos incorporar fragmentos de IU em um shell de aplicativo que ainda não existe? Resolvemos esse problema por meio de uma técnica que desenvolvemos e chamamos de “fragment piercing”.

Fragment piercing

O fragment piercing combina HTML/DOM produzido por fragmentos de micro front-end renderizados do lado do servidor com HTML/DOM produzidos por um aplicativo legado renderizado do lado do cliente.

Os fragmentos de micro front-end são renderizados diretamente no nível superior da resposta HTML e são projetados para se tornarem imediatamente interativos. Em segundo plano, o aplicativo legado é renderizado no lado do cliente como um irmão desses fragmentos. Quando estiver pronto, é realizado o fragment piercing no aplicativo legado, o DOM de cada fragmento é movido para seu local apropriado dentro do DOM do aplicativo legado, sem causar efeitos colaterais visuais ou perda de estado do lado do cliente, como foco, dados de formulário ou seleção de texto. Uma vez que o fragment piercing é realizado, o fragmento pode começar a se comunicar com o aplicativo legado, tornando-se efetivamente parte integrante dele.

Aqui, você pode ver um fragmento de “login” e o elemento “raiz” do aplicativo legado vazio no nível superior do DOM, antes do piercing.

<body>
  <div id="root"></div>
  <piercing-fragment-host fragment-id="login">
    <login q:container...>...</login>
  </piercing-fragment-host>
</body>

E aqui você pode ver que o fragment piercing foi realizado no div “login-page” no aplicativo legado renderizado.

<body>
  <div id="root">
    <header>...</header>
    <main>
      <div class="login-page">
        <piercing-fragment-outlet fragment-id="login">
          <piercing-fragment-host fragment-id="login">
            <login  q:container...>...</login>
          </piercing-fragment-host>
        </piercing-fragment-outlet>
      </div>
    </main>
    <footer>...</footer>
  </div>
</body>

Para evitar que o fragmento se mova e cause uma mudança de layout visível durante essa transição, aplicamos estilos CSS que posicionam o fragmento da mesma maneira antes e depois do piercing.

A qualquer momento, um aplicativo pode exibir qualquer ou nenhum número de fragmentos em que foi realizado o "fragment piercing". Essa técnica não se limita apenas ao carregamento inicial do aplicativo legado. Os fragmentos também podem ser adicionados e removidos de um aplicativo a qualquer momento. Isso permite que os fragmentos sejam renderizados em resposta às interações do usuário e ao roteamento do lado do cliente.

Com o fragment piercing, você pode começar a adotar gradualmente micro front-ends, um fragmento por vez. Você decide a granularidade dos fragmentos e quais partes do aplicativo serão transformadas em fragmentos. Os fragmentos não precisam usar a mesma estrutura da web, o que pode ser útil ao alternar pilhas ou durante uma integração pós-aquisição de vários aplicativos.

A demonstração do "Productivity Suite"

Como demonstração do fragment piercing e adoção incremental, desenvolvemos um aplicativo de demonstração, “Productivity Suite”, que permite aos usuários gerenciar listas de tarefas, ler notícias sobre hackers, etc. Implementamos o shell desse aplicativo como um aplicativo React renderizado do lado do cliente, uma escolha de tecnologia comum em aplicativos corporativos. Este é o nosso “aplicativo legado”. Existem três rotas no aplicativo que foram atualizadas para usar fragmentos de micro front-ends:

  • /login – um formulário de login fictício simples com validação do lado do cliente, exibido quando os usuários não são autenticados (implementado no Qwik).

  • /todos – gerencia uma ou mais listas de tarefas, implementadas como dois fragmentos de colaboração:

    • Seletor de lista de tarefas – um componente para selecionar/criar/excluir listas de tarefas (implementado no Qwik).

    • Editor de lista de tarefas – um clone do aplicativo TodoMVC (implementado no React).

  • /news – um clone do demo HackerNews (implementado em SolidJS).

Esta demonstração mostra que diferentes tecnologias independentes podem ser usadas tanto para o aplicativo legado quanto para cada um dos fragmentos.

Uma visualização dos fragmentos em que foi realizado o fragment piercing no aplicativo legado

O aplicativo está implantado em https://productivity-suite.web-experiments.workers.dev/.

Para experimentá-lo, primeiro você precisa fazer login, basta usar qualquer nome de usuário que desejar (a senha não é necessária). Os dados do usuário são salvos em um cookie, para que você possa sair e voltar usando o mesmo nome de usuário. Depois de fazer login, navegue pelas várias páginas usando a barra de navegação na parte superior do aplicativo. Dê uma olhada, em particular, nas páginas “Todo Lists” e “News” para ver o piercing em ação.

A qualquer momento, tente recarregar a página para ver se os fragmentos são renderizados instantaneamente enquanto o aplicativo legado carrega lentamente em segundo plano. Tente interagir com os fragmentos antes mesmo de o aplicativo legado aparecer.

No topo da página, há controles que permitem que você veja o impacto do fragment piercing em ação.

  • Use o controle deslizante “Legacy app bootstrap delay” para definir o atraso simulado antes do início do aplicativo legado.

  • Alterne para “Piercing Enabled” para ver qual seria a experiência do usuário se o aplicativo não usasse fragmentos.

  • Alterne para “Show Seams” para ver onde cada fragmento está na página atual.

Como funciona

O aplicativo é composto por vários elementos básicos.

Uma visão geral do Workers colaborando e do host de aplicativos legados

O host do aplicativo legado em nossa demonstração mostra os arquivos que definem o aplicativo React do lado do cliente (HTML, JavaScript e folhas de estilo). Aplicativos desenvolvidos com outras pilhas de tecnologia também funcionariam. Os Fragment Workers hospedam os fragmentos de micro front-end, conforme descrito em nosso post anterior sobre arquitetura de fragmentos. E o Gateway Worker lida com as solicitações do navegador, selecionando, buscando e combinando fluxos de resposta do aplicativo legado e fragmentos de micro front-end.

Depois que todas essas partes são implantadas, elas trabalham juntas para lidar com cada solicitação do navegador. Vejamos o que acontece quando você vai para a rota `/login`.

O fluxo de solicitações ao visualizar a página de login

O usuário navega até o aplicativo e o navegador faz uma solicitação ao Gateway Worker para obter o HTML inicial. O Gateway Worker identifica que o navegador está solicitando a página de login. Em seguida, ele faz duas subsolicitações paralelas, uma para buscar o index.html do aplicativo legado e outra para solicitar o fragmento de login renderizado do lado do servidor. Depois, ele combina essas duas respostas em um único fluxo de resposta contendo o HTML que é entregue ao navegador.

O navegador exibe a resposta HTML contendo o elemento raiz vazio para o aplicativo legado e o fragmento de login renderizado do lado do servidor, que fica imediatamente interativo para o usuário.

O navegador então solicita o JavaScript do aplicativo legado. Essa solicitação é representada pelo Gateway Worker para o host do aplicativo legado. Da mesma forma, quaisquer outros ativos para o aplicativo legado ou fragmentos são roteados por meio do Gateway Worker para o host do aplicativo legado ou o Fragment Worker apropriado.

Depois que o JavaScript do aplicativo legado é baixado e executado, renderizando o shell do aplicativo no processo, o fragment piercing entra em ação, movendo o fragmento para o local apropriado no aplicativo legado, preservando todo o estado da IU.

Focamos no fragmento de login para explicar o fragment piercing, mas as mesmas ideias se aplicam aos outros fragmentos implementados nas rotas/todos e /news.

A piercing library

Apesar de serem implementados usando diferentes estruturas da web, todos os fragmentos são integrados no aplicativo legado da mesma forma usando auxiliares da “Piercing Library”. Essa biblioteca é uma coleção de utilitários do lado do servidor e do lado do cliente que desenvolvemos, para a demonstração, para lidar com a integração do aplicativo legado com fragmentos de micro front-end. Os principais recursos da biblioteca são a classe PiercingGateway, elementos personalizados do host de fragmento e da saída de fragmento e a classe MessageBus.

PiercingGateway

A classe PiercingGateway pode ser usada para instanciar um Gateway Worker que lida com todas as solicitações de HTML, JavaScript e outros recursos de nosso aplicativo. O `PiercingGateway` roteia as solicitações para os Fragment Workers apropriados ou para o host do aplicativo legado. Ele também combina os fluxos de resposta HTML desses fragmentos com a resposta do aplicativo legado em um único fluxo HTML que é retornado ao navegador.

Implementar um Gateway Worker é simples usando a Piercing Library. Crie uma nova instância de gateway de PiercingGateway, passando o URL para o host do aplicativo legado e uma função para determinar se o piercing está habilitado para a determinada solicitação. Exporte o gateway como a exportação padrão do script do Worker para que o tempo de execução do Workers possa conectar seu controlador fetch().

const gateway = new PiercingGateway<Env>({
  // Configure the origin URL for the legacy application.
  getLegacyAppBaseUrl: (env) => env.APP_BASE_URL,
  shouldPiercingBeEnabled: (request) => ...,
});
...

export default gateway;

Os fragmentos podem ser registrados chamando o método registerFragment() para que o gateway possa rotear automaticamente as solicitações de HTML e ativos de um fragmento para seu Fragment Worker. Por exemplo, registrar o fragmento de login ficaria assim:

gateway.registerFragment({
  fragmentId: "login",
  prePiercingStyles: "...",
  shouldBeIncluded: async (request) => !(await isUserAuthenticated(request)),
});

Host e saída de fragmento

Rotear solicitações e combinar respostas HTML no Gateway Worker é apenas metade do que torna o piercing possível. A outra metade precisa acontecer no navegador, onde os fragmentos precisam ser inseridos no aplicativo legado usando a técnica que descrevemos anteriormente.

O fragment piercing no navegador é facilitado por um par de elementos personalizados, o host do fragmento(<piercing-fragment-host>) e a saída do fragmento (<piercing-fragment-outlet>).

O Gateway Worker agrupa o HTML para cada fragmento em um host de fragmento. No navegador, o host do fragmento gerencia o tempo de vida do fragmento e é usado ao mover o DOM do fragmento para a posição no aplicativo legado.

<piercing-fragment-host fragment-id="login">
  <login q:container...>...</login>
</piercing-fragment-host>

No aplicativo legado, o desenvolvedor marca onde um fragmento deve aparecer quando ocorre o fragment piercing adicionando uma saída de fragmento. A rota de login do nosso aplicativo de demonstração é semelhante a isto:

export function Login() {
  …
  return (
    <div className="login-page" ref={ref}>
      <piercing-fragment-outlet fragment-id="login" />
    </div>
  );
}

Quando uma saída de fragmento é adicionada ao DOM, ela procura no documento atual seu host de fragmento associado. Se encontrado, o host do fragmento e seu conteúdo são movidos para dentro da saída. Se o host do fragmento não for encontrado, a saída fará uma solicitação ao gateway worker para buscar o fragmento HTML, que será transmitido diretamente para a saída do fragmento, usando a biblioteca writable-dom (uma biblioteca pequena, mas poderosa, desenvolvida pela equipe MarkoJS).

Esse mecanismo de fallback permite a navegação do lado do cliente para rotas que contêm novos fragmentos. Dessa forma, os fragmentos podem ser renderizados no navegador por meio da navegação inicial (hard) e da navegação do lado do cliente (soft).

Message bus

A menos que os fragmentos em nosso aplicativo sejam totalmente de apresentação ou independentes, eles também precisam se comunicar com o aplicativo legado e outros fragmentos. O [MessageBus](https://github.com/cloudflare/workers-web-experiments/blob/df50b60cfff7bc299cf70ecfe8f7826ec9313b84/productivity-suite/piercing-library/src/message-bus/message-bus.ts#L18) é um barramento de comunicação assíncrono, isomórfico e independente de estrutura simples que o aplicativo legado e cada um dos fragmentos podem acessar.

Em nosso aplicativo de demonstração, o fragmento de login precisa informar ao aplicativo legado quando o usuário for autenticado. Este envio de mensagem é implementado no componente Qwik LoginForm da seguinte forma:

const dispatchLoginEvent = $(() => {
  getBus(ref.value).dispatch("login", {
    username: state.username,
    password: state.password,
  });
  state.loading = true;
});

O aplicativo legado pode receber essas mensagens desta forma:

useEffect(() => {
  return getBus().listen<LoginMessage>("login", async (user) => {
    setUser(user);
    await addUserDataIfMissing(user.username);
    await saveCurrentUser(user.username);
    getBus().dispatch("authentication", user);
    navigate("/", { replace: true, });
  });
}, []);

Escolhemos essa implementação do barramento de mensagens porque precisávamos de uma solução independente de estrutura e que funcionasse bem tanto no servidor quanto no cliente.

Experimente

Com fragmentos, fragment piercing e o Cloudflare Workers, você pode melhorar o desempenho, bem como o ciclo de desenvolvimento de aplicativos legados renderizados do lado do cliente. Essas alterações podem ser adotadas de forma incremental, e você pode até mesmo fazer isso ao implementar fragmentos com uma estrutura da web de sua escolha.

O aplicativo “Productivity Suite” que demonstra esses recursos pode ser encontrado em https://productivity-suite.web-experiments.workers.dev/.

Todo o código que mostramos é de código aberto e está publicado no Github: https://github.com/cloudflare/workers-web-experiments/tree/main/productivity-suite.

Sinta-se à vontade para clonar o repositório. É fácil executá-lo localmente e até mesmo implantar sua própria versão (gratuitamente) na Cloudflare. Tentamos tornar o código o mais reutilizável possível. A maior parte da lógica central está na piercing library que você pode experimentar em seus próprios projetos. Ficaremos felizes em receber feedback, sugestões ou ouvir sobre os aplicativos nos quais você gostaria de usá-lo. Participe de nossa discussão no GitHub ou entre em contato conosco em nosso canal do Discord.

Acreditamos que a combinação do Cloudflare Workers com as ideias mais recentes de estruturas impulsionará os próximos grandes passos em experiências aprimoradas para usuários e desenvolvedores em aplicativos web. Vamos ter mais demonstrações, posts no blog e colaborações à medida que continuamos a ultrapassar os limites do que a web pode oferecer. E se você também quiser fazer parte diretamente desta jornada, ficamos felizes em compartilhar que estamos contratando.

Protegemos redes corporativas inteiras, ajudamos os clientes a criarem aplicativos em escala de internet com eficiência, aceleramos qualquer site ou aplicativo de internet, evitamos os ataques de DDoS, mantemos os invasores afastados e podemos ajudar você em sua jornada rumo ao Zero Trust.

Acesse 1.1.1.1 a partir de qualquer dispositivo para começar a usar nosso aplicativo gratuito que torna sua internet mais rápida e mais segura.

Para saber mais sobre nossa missão de construir uma internet melhor, comece aqui. Se estiver procurando uma nova carreira para trilhar, confira nossas vagas disponíveis.
Developer WeekDesenvolvedoresCloudflare WorkersEdgeMicro-frontends (PT)Developer Platform

Seguir no X

Peter Bacon Darwin|@petebd
Igor Minar|@IgorMinar
Cloudflare|@cloudflare

Posts relacionados

31 de outubro de 2024 às 13:00

Moving Baselime from AWS to Cloudflare: simpler architecture, improved performance, over 80% lower cloud costs

Post-acquisition, we migrated Baselime from AWS to the Cloudflare Developer Platform and in the process, we improved query times, simplified data ingestion, and now handle far more events, all while cutting costs. Here’s how we built a modern, high-performing observability platform on Cloudflare’s network. ...

24 de outubro de 2024 às 13:05

Build durable applications on Cloudflare Workers: you write the Workflows, we take care of the rest

Cloudflare Workflows is now in open beta! Workflows allows you to build reliable, repeatable, long-lived multi-step applications that can automatically retry, persist state, and scale out. Read on to learn how Workflows works, how we built it on top of Durable Objects, and how you can deploy your first Workflows application....