Typed Contract Pipeline (TCP) - DevX e escala real em Microfrontends
Microfrontends não falham por falta de tecnologia. Eles falham quando a experiência de desenvolvimento (DevX) se deteriora à medida que o sistema cresce. Este artigo apresenta o Typed Contract Pipeline (TCP), uma abordagem de plataforma aplicada a arquiteturas de Microfrontends com single-spa + importmap, com foco em DevX e escala organizacional.
Microfrontends não falham por falta de tecnologia.
Eles falham quando a experiência de desenvolvimento (DevX) se deteriora à medida que o sistema cresce.
No início, a arquitetura entrega o que promete:
- times independentes
- deploy desacoplado
- ownership claro
Com o tempo, surgem problemas recorrentes:
- contratos quebrando silenciosamente
- dependências internas se multiplicando
- circularidade entre libs
- atualizações manuais constantes
- erros descobertos apenas em produção
- dificuldade para rastrear jornadas do usuário
Este artigo apresenta uma abordagem de plataforma, não de ferramenta:
o Typed Contract Pipeline (TCP), aplicado a arquiteturas de Microfrontends com single-spa + importmap, com foco em DevX e escala organizacional.
Importmaps resolvem runtime, não contratos
Importmaps são excelentes para:
- controlar versões remotamente
- evitar rebuilds globais
- permitir deploy independente
Mas importmaps não carregam tipagem.
Eles resolvem runtime, não integração.
Em arquiteturas distribuídas, isso cria um risco estrutural:
mudanças compatíveis em runtime podem ser incompatíveis em integração — e o TypeScript não vê.
Tipos não são detalhe. São contratos.
Em microfrontends:
- props são contratos
- eventos são contratos
- hooks expostos são contratos
- contextos compartilhados são contratos
Se esses contratos não forem:
- explícitos
- versionados
- distribuídos automaticamente
o sistema escala em número de times, mas não em confiança.
O que é o Typed Contract Pipeline (TCP)
O Typed Contract Pipeline trata contratos tipados como artefatos de primeira classe da plataforma.
Definição objetiva
Typed Contract Pipeline é um pipeline automatizado que extrai contratos tipados dos componentes centrais da plataforma, agrega esses contratos em um pacote único e governado (
@org/platform-types) e os distribui automaticamente para todos os consumidores, garantindo segurança de tipos em tempo de desenvolvimento — independente do runtime.
Princípios centrais:
- runtime e compile-time são tratados separadamente
- contratos são públicos e explícitos
- automação substitui coordenação manual
Um cenário comum (e silenciosamente perigoso)
Imagine um Design System compartilhado.
Versão atual:
export interface ButtonProps {
variant: "primary" | "secondary";
size: "sm" | "md";
}
O time A consome via importmap:
<script type="importmap">
{
"imports": {
"@org/design-system": "https://cdn.example.com/[email protected]/index.js"
}
}
</script>
E usa em TypeScript:
import { Button } from '@org/design-system';
<Button variant="primary" size="sm" />
Tudo funciona. O TypeScript valida, o runtime carrega.
O problema silencioso
Duas semanas depois, o time do Design System adiciona uma nova prop:
export interface ButtonProps {
variant: "primary" | "secondary" | "tertiary"; // nova opção
size: "sm" | "md" | "lg"; // nova opção
loading?: boolean; // nova prop opcional
}
E publica @org/[email protected].
O importmap do time A ainda aponta para 1.2.3.
O TypeScript do time A ainda vê os tipos de 1.2.3 (se tiverem instalado localmente).
O runtime carrega 1.2.3.
Tudo continua funcionando.
Mas o time B atualiza o importmap para 1.3.0 e começa a usar:
<Button variant="tertiary" size="lg" loading={true} />
O time A, sem saber, tenta usar variant="tertiary" baseado em documentação desatualizada ou comunicação verbal.
O TypeScript do time A não reclama (porque os tipos locais ainda são 1.2.3).
O runtime do time A não reclama (porque o importmap ainda aponta para 1.2.3).
Em desenvolvimento, tudo parece OK.
Em produção, quando o importmap é atualizado para 1.3.0 sem aviso, o código do time A quebra silenciosamente — porque variant="tertiary" não existe em 1.2.3, mas o TypeScript nunca avisou.
Por que isso acontece
- Tipos locais desincronizados: Cada time pode ter uma versão diferente dos tipos instalada localmente.
- Importmap independente: O importmap controla o runtime, mas não sincroniza tipos.
- Falta de visibilidade: Ninguém vê que o time A está usando uma versão antiga até quebrar.
- Coordenação manual: Depende de comunicação humana para sincronizar versões.
Como o TCP resolve
O Typed Contract Pipeline automatiza a extração, agregação e distribuição de contratos tipados.
Fluxo do TCP
Implementação prática
1. Extração automática de contratos
// scripts/extract-contracts.ts
import { extractTypes } from '@tcp/extractor';
import { writeFileSync } from 'fs';
const contracts = extractTypes({
packages: [
'@org/design-system',
'@org/shared-hooks',
'@org/event-bus'
],
output: './dist/contracts.d.ts'
});
writeFileSync('./dist/contracts.d.ts', contracts);
2. Agregação em pacote único
// packages/platform-types/package.json
{
"name": "@org/platform-types",
"version": "1.3.0",
"types": "./index.d.ts",
"exports": {
"./design-system": "./design-system.d.ts",
"./hooks": "./hooks.d.ts",
"./events": "./events.d.ts"
}
}
3. Distribuição automática
# .github/workflows/tcp-pipeline.yml
name: TCP Pipeline
on:
push:
branches: [main]
paths:
- 'packages/design-system/**'
- 'packages/shared-hooks/**'
jobs:
extract-and-publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- name: Extract contracts
run: npm run extract-contracts
- name: Publish @org/platform-types
run: npm publish --access public
- name: Notify teams
run: npm run notify-teams
4. Consumo automático nos times
// apps/time-a/package.json
{
"devDependencies": {
"@org/platform-types": "^1.3.0"
}
}
// apps/time-a/src/components/MyButton.tsx
import type { ButtonProps } from '@org/platform-types/design-system';
// TypeScript agora sempre vê a versão mais recente
const MyButton = (props: ButtonProps) => {
// Se tentar usar 'tertiary' e a versão local for 1.2.3,
// TypeScript reclama ANTES de ir para produção
};
Runtime federation with compile-time safety
O TCP separa runtime federation (importmap) de compile-time safety (tipos).
Arquitetura híbrida
Benefícios da separação
- TypeScript sempre atualizado:
@org/platform-typesé atualizado automaticamente via TCP. - Runtime controlado: Importmap permite controle de versão em produção sem rebuild.
- Detecção precoce: Incompatibilidades são detectadas em desenvolvimento, não em produção.
- Coordenação zero: Times não precisam se comunicar sobre mudanças de tipos.
Exemplo prático
Cenário: Design System adiciona variant="tertiary".
Com TCP:
- Pipeline extrai tipos automaticamente.
@org/[email protected]é publicado.- Time A atualiza dependência:
npm install @org/platform-types@latest. - TypeScript agora vê
variant="tertiary"como válido. - Se o time A tentar usar
variant="tertiary"mas o importmap ainda apontar para1.2.3, o TypeScript não reclama (porque os tipos são compatíveis para frente). - Quando o importmap for atualizado para
1.3.0, tudo funciona.
Sem TCP:
- Time A não sabe que
variant="tertiary"existe. - TypeScript não valida porque tipos locais são
1.2.3. - Time A pode tentar usar
variant="tertiary"baseado em documentação. - Em produção, quando importmap atualiza, código quebra.
Conclusão
Microfrontends escalam quando:
- runtime é desacoplado
- contratos são explícitos
- automação substitui coordenação
- DevX é tratado como produto
O Typed Contract Pipeline não é uma ferramenta.
É uma mudança de mentalidade.
Autonomia sem contrato é caos.
Contrato sem automação é burocracia.
TCP resolve os dois.