✍️ Autor e Código-Fonte
Este artigo documenta um projeto real e de código aberto.
- Criado por: Ghabryel Henrique
- 🐙 GitHub: GhabryelHenrique
- 💼 LinkedIn: Conecte-se Comigo
Explore o código-fonte completo nos repositórios oficiais do projeto:
- 🖥️ Aplicação Frontend (Angular): angular-mfe-example
Nos últimos anos, a arquitetura de Micro Frontends (MFE) tem ganhado destaque como solução para times que precisam escalar aplicações frontend de forma independente. Neste artigo, vou mostrar como implementar MFE em Angular usando Module Federation do Webpack 5.
O que são Micro Frontends?
Micro Frontends é um padrão arquitetural que estende os conceitos de microserviços para o frontend. A ideia é dividir uma aplicação monolítica em partes menores e independentes, onde cada time pode desenvolver, testar e fazer deploy de forma autônoma.
Principais Benefícios
- Autonomia dos times: Cada equipe trabalha em seu próprio repositório e ciclo de deploy
- Escalabilidade técnica: Diferentes tecnologias e versões podem coexistir
- Deploy independente: Alterações em um módulo não exigem rebuild de toda aplicação
- Manutenibilidade: Código mais organizado e com responsabilidades bem definidas
Module Federation: O Game Changer
Module Federation é uma funcionalidade nativa do Webpack 5 que permite compartilhar código entre aplicações JavaScript em tempo de execução, sem precisar recompilar ou fazer bundle novamente.
Como funciona?
O Module Federation divide as aplicações em dois tipos:
- Host (Shell): Aplicação principal que consome os módulos remotos
- Remote: Aplicação que expõe módulos para serem consumidos
Implementando na Prática
Vamos criar um exemplo com uma aplicação shell (host) e dois micro frontends remotos.
Passo 1: Configurando o Projeto
Primeiro, vamos usar o @angular-architects/module-federation para facilitar a configuração:
# Instalar o Angular CLI
npm install -g @angular/cli
# Criar workspace
ng new mfe-workspace --create-application=false
cd mfe-workspace
# Criar aplicações
ng generate application shell --routing --style=scss
ng generate application mfe-products --routing --style=scss
ng generate application mfe-orders --routing --style=scss
Passo 2: Adicionar Module Federation
Instale o plugin em cada aplicação:
# No diretório raiz
ng add @angular-architects/module-federation --project shell --port 4200 --type host
ng add @angular-architects/module-federation --project mfe-products --port 4201 --type remote
ng add @angular-architects/module-federation --project mfe-orders --port 4202 --type remote
Passo 3: Configurar o Remote (mfe-products)
No arquivo webpack.config.js do mfe-products:
const { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');
module.exports = withModuleFederationPlugin({
name: 'mfeProducts',
exposes: {
'./ProductsModule': './projects/mfe-products/src/app/products/products.module.ts',
},
shared: {
...shareAll({
singleton: true,
strictVersion: true,
requiredVersion: 'auto'
}),
},
});
Crie o módulo de produtos:
// products.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule, Routes } from '@angular/router';
import { ProductsComponent } from './products.component';
const routes: Routes = [
{
path: '',
component: ProductsComponent
}
];
@NgModule({
declarations: [ProductsComponent],
imports: [
CommonModule,
RouterModule.forChild(routes)
]
})
export class ProductsModule { }
Passo 4: Configurar o Host (Shell)
No arquivo webpack.config.js do shell:
const { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');
module.exports = withModuleFederationPlugin({
remotes: {
"mfeProducts": "http://localhost:4201/remoteEntry.js",
"mfeOrders": "http://localhost:4202/remoteEntry.js",
},
shared: {
...shareAll({
singleton: true,
strictVersion: true,
requiredVersion: 'auto'
}),
},
});
Configure as rotas no app-routing.module.ts:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { loadRemoteModule } from '@angular-architects/module-federation';
const routes: Routes = [
{
path: 'products',
loadChildren: () =>
loadRemoteModule({
type: 'module',
remoteEntry: 'http://localhost:4201/remoteEntry.js',
exposedModule: './ProductsModule'
})
.then(m => m.ProductsModule)
},
{
path: 'orders',
loadChildren: () =>
loadRemoteModule({
type: 'module',
remoteEntry: 'http://localhost:4202/remoteEntry.js',
exposedModule: './OrdersModule'
})
.then(m => m.OrdersModule)
},
{
path: '',
redirectTo: '/products',
pathMatch: 'full'
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Passo 5: Configuração Dinâmica
Para ambientes diferentes, crie um arquivo mfe.manifest.json:
{
"mfeProducts": "http://localhost:4201/remoteEntry.js",
"mfeOrders": "http://localhost:4202/remoteEntry.js"
}
Carregue o manifesto dinamicamente:
// main.ts
import { loadManifest } from '@angular-architects/module-federation';
loadManifest('/assets/mfe.manifest.json')
.catch(err => console.error('Error loading manifest', err))
.then(() => import('./bootstrap'))
.catch(err => console.error(err));
Compartilhamento de Estado
Para comunicação entre MFEs, você pode usar diferentes estratégias:
1. Usando RxJS e Services Compartilhados
// shared-state.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class SharedStateService {
private userSubject = new BehaviorSubject<any>(null);
public user$: Observable<any> = this.userSubject.asObservable();
setUser(user: any): void {
this.userSubject.next(user);
}
}
2. Usando Custom Events
// Emitir evento
window.dispatchEvent(new CustomEvent('user-updated', {
detail: { userId: 123 }
}));
// Ouvir evento
window.addEventListener('user-updated', (event: any) => {
console.log('User updated:', event.detail);
});
Boas Práticas
1. Versionamento
Use semantic versioning para suas bibliotecas compartilhadas:
shared: {
'@angular/core': {
singleton: true,
strictVersion: true,
requiredVersion: '^17.0.0'
}
}
2. Tratamento de Erros
Implemente error boundaries para falhas no carregamento de MFEs:
loadChildren: () =>
loadRemoteModule({
type: 'module',
remoteEntry: 'http://localhost:4201/remoteEntry.js',
exposedModule: './ProductsModule'
})
.then(m => m.ProductsModule)
.catch(err => {
console.error('Error loading module', err);
return import('./fallback/fallback.module').then(m => m.FallbackModule);
})
3. Performance
- Use lazy loading para carregar MFEs sob demanda
- Implemente caching strategies para remoteEntry.js
- Monitore o tamanho dos bundles compartilhados
4. Testes
Configure testes para cada MFE independentemente:
// products.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ProductsComponent } from './products.component';
describe('ProductsComponent', () => {
let component: ProductsComponent;
let fixture: ComponentFixture<ProductsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ProductsComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ProductsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
Deploy e CI/CD
Para deploy independente, configure pipelines separados:
# .github/workflows/mfe-products.yml
name: Deploy MFE Products
on:
push:
branches: [ main ]
paths:
- 'projects/mfe-products/**'
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build:mfe-products
- name: Deploy
run: |
# Deploy para seu CDN/Storage
aws s3 sync dist/mfe-products s3://your-bucket/mfe-products
Desafios e Considerações
1. Debugging
Debugging pode ser mais complexo. Use source maps e ferramentas como:
// webpack.config.js
module.exports = {
devtool: 'source-map',
// ...
}
2. Dependências Compartilhadas
Cuidado com o tamanho do bundle. Use o bundle analyzer:
npm install --save-dev webpack-bundle-analyzer
3. Consistência de UI
Crie uma biblioteca de design system compartilhada:
ng generate library ui-components
Conclusão
Module Federation transformou a forma como construímos Micro Frontends em Angular. Com ele, conseguimos:
- Deploy independente de aplicações
- Compartilhamento de código em runtime
- Redução significativa no tamanho dos bundles
- Maior autonomia para os times
A arquitetura de MFE não é uma bala de prata e adiciona complexidade ao projeto, mas para times grandes trabalhando em aplicações enterprise, os benefícios superam os desafios.
Recursos Úteis
- Module Federation Documentation
- Angular Architects Module Federation
- Micro Frontends by Martin Fowler
Repositório de Exemplo
Você pode encontrar o código completo deste tutorial no GitHub: [https://github.com/GhabryelHenrique/angular-mfe-example]
O que você achou deste artigo? Já trabalha com Micro Frontends? Compartilhe sua experiência nos comentários!
Se este conteúdo foi útil, não esqueça de dar um ❤️ e compartilhar com outros devs!
Top comments (0)