Micro-frontends: Quando e Como Implementar
Arquitetura
28 de dezembro de 2023
15 min de leitura

Micro-frontends: Quando e Como Implementar

Lições aprendidas implementando arquiteturas de micro-frontends em grandes empresas de tecnologia.

#Micro-frontends #Arquitetura #Webpack #Module Federation #Escalabilidade
Micro-frontends: Quando e Como Implementar

Micro-frontends: Quando e Como Implementar

Depois de implementar micro-frontends em grandes empresas, compartilho aqui quando essa arquitetura realmente vale a pena e como fazer direito.

O que São Micro-frontends?

Micro-frontends estendem o conceito de microserviços para o frontend, permitindo que equipes independentes desenvolvam, testem e deployem partes da aplicação de forma autônoma.

Quando Implementar?

✅ Cenários Ideais:

  1. Múltiplas equipes (5+ times) trabalhando no mesmo produto
  2. Tecnologias diferentes necessárias em partes da aplicação
  3. Deploy independente é prioridade
  4. Escalabilidade da equipe é um problema

❌ Quando NÃO usar:

  1. Equipe pequena (menos de 10 devs)
  2. Aplicação simples sem complexidade de domínio
  3. Performance é crítica (overhead de rede)
  4. Shared state complexo entre módulos

Experiência no QuintoAndar

Implementamos micro-frontends para o portal do corretor, onde tínhamos:

  • 3 equipes diferentes
  • React + Vue.js na mesma aplicação
  • Deploys independentes por funcionalidade

Arquitetura Implementada:

// Shell Application (Container)
const RemoteComponent = React.lazy(
  () => import("propertyModule/PropertySearch")
);

const App = () => (
  <Router>
    <Header />
    <Routes>
      <Route
        path="/properties/*"
        element={
          <Suspense fallback={<Spinner />}>
            <RemoteComponent />
          </Suspense>
        }
      />
      <Route path="/profile/*" element={<ProfileModule />} />
    </Routes>
    <Footer />
  </Router>
);

Implementação com Module Federation

1. Configuração do Host (Shell)

// webpack.config.js (Host)
const ModuleFederationPlugin = require("@module-federation/webpack");

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: "shell",
      remotes: {
        propertyModule: "property@http://localhost:3001/remoteEntry.js",
        userModule: "user@http://localhost:3002/remoteEntry.js",
      },
      shared: {
        react: { singleton: true },
        "react-dom": { singleton: true },
      },
    }),
  ],
};

2. Configuração do Remote (Módulo)

// webpack.config.js (Remote)
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: "property",
      filename: "remoteEntry.js",
      exposes: {
        "./PropertySearch": "./src/PropertySearch",
        "./PropertyDetails": "./src/PropertyDetails",
      },
      shared: {
        react: { singleton: true },
        "react-dom": { singleton: true },
      },
    }),
  ],
};

3. Componente Exportado

// PropertySearch.tsx (Remote)
import React from "react";

interface PropertySearchProps {
  onSearch?: (query: string) => void;
  theme?: "light" | "dark";
}

const PropertySearch: React.FC<PropertySearchProps> = ({
  onSearch,
  theme = "light",
}) => {
  return (
    <div className={`search-container ${theme}`}>
      {/* Implementação do componente */}
    </div>
  );
};

export default PropertySearch;

Patterns de Comunicação

1. Event Bus Pattern

// events.ts
class EventBus {
  private events: { [key: string]: Function[] } = {};

  subscribe(event: string, callback: Function) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(callback);
  }

  emit(event: string, data?: any) {
    if (this.events[event]) {
      this.events[event].forEach((callback) => callback(data));
    }
  }

  unsubscribe(event: string, callback: Function) {
    if (this.events[event]) {
      this.events[event] = this.events[event].filter((cb) => cb !== callback);
    }
  }
}

export const eventBus = new EventBus();

2. Shared State com Context

// SharedContext.tsx
interface SharedState {
  user: User | null;
  theme: "light" | "dark";
  language: string;
}

const SharedContext = React.createContext<SharedState | null>(null);

// No Shell App
const App = () => {
  const [sharedState, setSharedState] = useState<SharedState>({
    user: null,
    theme: "light",
    language: "pt-BR",
  });

  return (
    <SharedContext.Provider value={sharedState}>
      {/* Micro-frontends aqui */}
    </SharedContext.Provider>
  );
};

Desafios e Soluções

1. Versionamento de Dependências

Problema: Conflitos entre versões do React/Vue

Solução:

// webpack.config.js
shared: {
  react: {
    singleton: true,
    requiredVersion: '^18.0.0',
    strictVersion: true
  },
}

2. Roteamento Global

Problema: Navegação entre micro-frontends

Solução:

// Router.tsx
import { createBrowserHistory } from "history";

// Histórico compartilhado
export const sharedHistory = createBrowserHistory();

// Cada micro-frontend usa o mesmo histórico
const MicroFrontendRouter = ({ children }) => (
  <Router navigator={sharedHistory}>{children}</Router>
);

3. Design System Compartilhado

Problema: Consistência visual

Solução:

// Design System como micro-frontend separado
const designSystem = {
  name: "designSystem",
  exposes: {
    "./Button": "./src/Button",
    "./Input": "./src/Input",
    "./Theme": "./src/Theme",
  },
};

// Usado por todos os micro-frontends
import { Button } from "designSystem/Button";

Performance Considerations

Bundle Splitting Inteligente

// webpack.config.js
optimization: {
  splitChunks: {
    chunks: 'all',
    cacheGroups: {
      vendor: {
        test: /[\\/]node_modules[\\/]/,
        name: 'vendors',
        priority: 10,
      },
      shared: {
        name: 'shared',
        minChunks: 2,
        priority: 5,
      },
    },
  },
}

Lazy Loading Otimizado

// Preload estratégico
const PropertyModule = React.lazy(() => {
  // Preload quando usuário hovera no menu
  const preload = () => import("propertyModule/PropertySearch");

  return import("propertyModule/PropertySearch");
});

// Uso com preload
<MenuItem
  onMouseEnter={() => PropertyModule.preload?.()}
  onClick={() => navigate("/properties")}
>
  Imóveis
</MenuItem>;

Monitoramento e Debug

Error Boundaries por Módulo

class MicroFrontendErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, moduleName: props.moduleName };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // Log específico por módulo
    analytics.track("Micro-frontend Error", {
      module: this.state.moduleName,
      error: error.message,
      stack: errorInfo.componentStack,
    });
  }

  render() {
    if (this.state.hasError) {
      return (
        <div>
          <h2>Algo deu errado no módulo {this.state.moduleName}</h2>
          <button onClick={() => window.location.reload()}>
            Recarregar página
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

Resultados Alcançados

No QuintoAndar:

  • Deploy time: 45min → 8min por módulo
  • Time to market: -60% para novas features
  • Developer experience muito melhor
  • Autonomia total das equipes

No Netshoes:

  • 3 tecnologias diferentes na mesma app
  • Zero downtime em deploys parciais
  • A/B tests por módulo independente

Ferramentas Recomendadas

  1. Webpack Module Federation - Standard do mercado
  2. Single SPA - Framework agnóstico
  3. Bit - Component sharing
  4. Nx - Monorepo com micro-frontends

Conclusão

Micro-frontends não são bala de prata. São uma solução arquitetural para problemas específicos de escala e autonomia.

Implemente quando:

  • Múltiplas equipes precisam de autonomia
  • Deploy independente é crítico
  • Tecnologias diferentes são necessárias

Próximo passo: Avalie se sua aplicação realmente precisa dessa complexidade antes de implementar.


Você já trabalhou com micro-frontends? Qual foi sua experiência? Compartilhe nos comentários!

Logo Renato Khael

Renato Khael

Engenheiro de Software

Gostou do artigo?

Vamos conversar sobre seu próximo projeto ou trocar ideias sobre tecnologia

WhatsApp