Dividir um Monólito em 47 Serviços—Depois Passar 18 Meses a Juntá-lo de Novo
Uma história comum na indústria. Uma arquitetura inicial de microsserviços torna-se um monólito distribuído—47 serviços que todos precisam de ser deployed juntos, não conseguem funcionar independentemente, e transformam cada release de feature num pesadelo de coordenação. A latência triplica. O debugging leva horas em vez de minutos. As equipas passam mais tempo em infraestrutura do que em features.
A lição não é que microsserviços são maus. É que microsserviços sem disciplina são piores que um monólito bem estruturado. Após 18 meses de consolidação e redesign, plataformas chegam a 12 serviços que realmente funcionam. Eis as lições chave.
Quando Microsserviços Fazem Sentido (E Quando Não)
O Framework de Decisão de Microsserviços
Antes de dividir seja o que for, pergunta:
1. Tens requisitos de scaling independentes?
Se o teu serviço de inferência IA precisa de 100 GPUs enquanto a autenticação de utilizador precisa de 2 CPUs, serviços separados fazem sentido. Se tudo escala junto, estás a adicionar complexidade sem benefício.
2. Equipas diferentes são donas de domínios diferentes?
Microsserviços permitem autonomia de equipa. Se uma única equipa é dona de tudo, estás a criar overhead de comunicação sem benefício organizacional.
3. Precisas de deployability independente?
Se genuinamente precisas de fazer deploy do serviço de pagamentos sem tocar no serviço de notificações, microsserviços ajudam. Se fazes sempre deploy de tudo junto de qualquer forma, tens um monólito distribuído.
4. As fronteiras de domínio são claras e estáveis?
Dividir em fronteiras incertas significa refactoring constante à medida que aprendes o teu domínio. Começa com um monólito modular; extrai serviços quando as fronteiras estabilizarem.
O Que Devíamos Ter Feito
Fase 1 (Mês 0-6): Monólito Modular
- Single deployable com módulos claramente separados
- Cada módulo com o seu próprio schema de base de dados
- APIs internas entre módulos, mas contexto de transação único
- Ownership de equipa ao nível do módulo
Fase 2 (Mês 6-12): Extração Estratégica
- Identificar o módulo com fronteira mais clara e necessidade de scaling mais independente
- Extrair para um serviço, manter tudo o resto monolítico
- Aprender padrões operacionais com baixo raio de explosão
Fase 3 (Contínua): Arquitetura Evolutiva
- Extrair serviços adicionais apenas quando benefícios específicos são claros
- Consolidar serviços que não justificaram a sua complexidade
Padrões Essenciais de Microsserviços para Aplicações IA
Padrão 1: API Gateway com Routing Específico para IA
Workloads IA têm necessidades de routing únicas: seleção de modelo, tratamento de fallback e enforcement de quotas devem acontecer antes dos pedidos chegarem aos serviços backend.
Arquitetura:
┌─────────────────────────────────────────────────┐
│ API Gateway │
│ ┌────────────┬────────────┬────────────────┐ │
│ │ Auth & │ Rate │ AI Model │ │
│ │ Quota │ Limiting │ Router │ │
│ └────────────┴────────────┴────────────────┘ │
└──────────────────────┬──────────────────────────┘
│
┌───────────────┼───────────────┐
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Chat │ │ Image │ │ Embeddings │
│ Service │ │ Service │ │ Service │
└─────────────┘ └─────────────┘ └─────────────┘
Responsabilidades chave do gateway:
- Validar API keys e verificar saldos de créditos antes de reencaminhar
- Encaminhar para serviço apropriado baseado no tipo de pedido
- Implementar circuit breakers para serviços downstream
- Recolher métricas transversais (latência, taxas de erro, uso de tokens)
A nossa implementação: Usamos Kong Gateway com plugins customizados para lógica específica de IA. O plugin de router de modelo examina o body do pedido, determina o backend ótimo baseado no modelo pedido e saúde atual do sistema, e injeta headers de routing.
Padrão 2: Saga Pattern para Workflows Multi-Modelo
Workflows IA complexos frequentemente envolvem múltiplos modelos em sequência. O que acontece quando o passo 3 de 5 falha?
Abordagem de orquestração Saga:
Workflow: Pipeline de Análise de Documentos
Passo 1: Extrair texto (OCR Service) → Em falha: retornar erro
Passo 2: Sumarizar (LLM Service) → Em falha: retornar resultado parcial
Passo 3: Classificar (ML Service) → Em falha: usar classificação default
Passo 4: Armazenar resultados (Storage Service) → Em falha: colocar em fila para retry
Passo 5: Notificar (Notification Service) → Em falha: log, não bloquear
Estratégias de compensação:
- Forward recovery: Retry com exponential backoff
- Fallback: Usar um modelo alternativo mais barato/rápido
- Conclusão parcial: Retornar resultados dos passos concluídos
- Rollback completo: Desfazer todos os passos (raramente necessário para workloads IA read-heavy)
Dica de implementação: Usa um workflow engine (Temporal, Cadence, ou mesmo uma state machine simples) em vez de hardcodar lógica de saga. Gestão de estado explícita torna debugging e recovery vastamente mais fáceis.
Padrão 3: Sidecar Pattern para Inferência de Modelo
Faz deploy de containers de inferência como sidecars dos teus containers de aplicação, não como serviços separados.
Porquê:
- Elimina latência de rede para chamadas de inferência
- Simplifica service discovery (sempre localhost)
- Permite acoplamento apertado de recursos (escala pods, não serviços)
Arquitetura:
Kubernetes pod spec
spec:
containers:
- name: application
image: myapp:latest
ports:
- containerPort: 8080 - name: inference-sidecar
image: llama-inference:latest
ports:
- containerPort: 8081 # Apenas acessível dentro do pod
resources:
limits:
nvidia.com/gpu: 1
Quando usar: Requisitos de inferência de baixa latência, necessidades de localidade de dados (contexto grande que é caro de transferir), ou ambientes regulados onde dados não devem sair do pod.
Quando não usar: Serving de modelo partilhado entre muitas aplicações (usa um serviço dedicado de modelo), ou quando pods se tornariam demasiado pesados em recursos.
Padrão 4: Processamento IA Event-Driven
Desacopla processamento IA de request/response síncrono onde possível.
Fluxo de eventos:
Pedido User → API → Fila Mensagens → Workers IA → Store Resultados → Notificação
↑ │
└──────── Fila Retry ←─────────┘
Schema de mensagem para tarefas IA:
{
"task_id": "uuid",
"task_type": "image_generation",
"priority": "standard",
"parameters": {
"model": "dall-e-3",
"prompt": "...",
"size": "1024x1024"
},
"callback_url": "https://...",
"max_retries": 3,
"timeout_seconds": 120,
"created_at": "2024-12-15T10:30:00Z"
}
Benefícios:
- Tratamento natural de backpressure (profundidade da fila = indicador de carga)
- Padrões de retry e dead-letter queue incorporados
- Scaling horizontal mais fácil (adicionar workers conforme necessário)
- Domínios de falha desacoplados
Padrão 5: Service Mesh para Observabilidade IA
Serviços IA precisam de observabilidade além de métricas HTTP standard. Uma service mesh como Istio ou Linkerd fornece a fundação; métricas customizadas fornecem insights específicos de IA.
Métricas IA essenciais a recolher:
- Uso de tokens por modelo por cliente
- Distribuição de latência do modelo (P50, P95, P99)
- Taxas de cache hit para prompts repetidos
- Taxas de erro por tipo de erro (rate limit, comprimento de contexto, filtro de conteúdo)
- Custo por pedido (calculado do uso de tokens)
Setup de tracing distribuído:
Cada pedido IA deve carregar um trace ID através de:
- API Gateway (trace criado)
- Decisão de routing (span: seleção de modelo)
- Chamada ao provider (span: inferência, inclui modelo e contagem de tokens)
- Pós-processamento (span: formatação de output)
- Resposta (trace concluído)
As nossas anotações de tracing:
@tracer.span("ai_inference")
async def call_model(request: InferenceRequest):
span = tracer.current_span()
span.set_attribute("model", request.model)
span.set_attribute("input_tokens", count_tokens(request.prompt)) response = await provider.complete(request)
span.set_attribute("output_tokens", count_tokens(response.text))
span.set_attribute("provider_latency_ms", response.latency_ms)
return response
Anti-Padrões Que Aprendemos da Maneira Difícil
Anti-Padrão 1: Serviços Conversadores
O que fizemos de errado: O nosso "user service" fazia 7 chamadas API a outros serviços para construir um perfil de utilizador.
O problema: 7 chamadas sequenciais = multiplicação de latência. Um serviço lento = tudo lento.
A correção: Desnormalizar modelos de leitura. O user service armazena tudo o que precisa para responder a queries sem chamar outros serviços. Updates propagam via eventos.
Anti-Padrão 2: Base de Dados Partilhada
O que fizemos de errado: Três serviços todos acediam à mesma base de dados PostgreSQL "por simplicidade."
O problema: Mudanças de schema requeriam coordenar três equipas. A carga de queries pesada de um serviço afetava todos os três. Tínhamos acoplamento sem os benefícios.
A correção: Base de dados por serviço, sem exceções. Serviços comunicam via APIs ou eventos, nunca via acesso partilhado à base de dados.
Anti-Padrão 3: Tudo Síncrono
O que fizemos de errado: Cada chamada de serviço era HTTP síncrono. Uma cadeia de 5 serviços significava que o serviço mais lento determinava a latência global.
O problema: Falhas em cascata. Quando o serviço mais lento degradava, tudo degradava.
A correção: Async por defeito. Se um serviço não precisa de resposta imediata, publica um evento. Usa chamadas síncronas apenas quando o caller genuinamente precisa de esperar.
Anti-Padrão 4: Serviços Sub-Instrumentados
O que fizemos de errado: Fizemos deploy de serviços sem métricas abrangentes. "Adicionamos observabilidade depois."
O problema: Quando as coisas quebraram, não conseguíamos diagnosticar porquê. Horas gastas a adicionar instrumentação durante incidentes.
A correção: Observabilidade não é opcional. Cada serviço é shipped com: endpoints de health, logging estruturado, exportação de métricas e propagação de traces. Sem exceções.
A Arquitetura de 12 Serviços Que Funciona
Após consolidação, a nossa plataforma IA corre em 12 serviços:
- Gateway - Autenticação, routing, rate limiting
- Chat - Workloads IA conversacionais
- Completion - Geração de texto single-turn
- Embeddings - Geração de vetores
- Images - Geração e análise de imagens
- Audio - Speech-to-text, text-to-speech
- Credits - Tracking de uso e faturação
- Models - Registo de modelos e monitorização de saúde
- Jobs - Processamento de tarefas async
- Storage - Armazenamento de ficheiros e resultados
- Events - Event bus e notificações
- Admin - Tooling interno e dashboards
Cada serviço tem ownership clara, características de scaling independentes, e pode ser deployed sem afetar outros. Este não foi o nosso ponto de partida—é onde chegámos após aprender que fronteiras realmente importam.
A melhor arquitetura de microsserviços é aquela que consegues operar. Começa mais simples do que pensas que precisas, adiciona complexidade apenas quando a ganhaste, e lembra-te sempre: sistemas distribuídos são difíceis, e cada chamada de rede é um potencial ponto de falha.