Índice
- O que é o padrão Strangler Fig
- Por que "reescrever tudo de uma vez" dá errado
- Como funciona, passo a passo
- O Anti-Corruption Layer (ACL)
- Roteamento: API Gateway e feature flags
- Migração de banco de dados
- Quando parar de migrar
- Exemplo prático: extraindo o checkout
- Anti-padrões e armadilhas reais
- Erros comuns
- Checklist de migração
O que é o padrão Strangler Fig {#o-que-e}
O Strangler Fig é um padrão de migração incremental descrito por Martin Fowler em 2004. O nome vem de uma árvore real, a figueira estranguladora (strangler fig), uma planta que cresce ao redor de outra árvore hospedeira. Ela vai envolvendo o tronco aos poucos, ganhando espaço, até que um dia a árvore original morre e a figueira ocupa seu lugar — sem que em nenhum momento a floresta ficou sem árvore.
A analogia no software é direta: em vez de derrubar o monolito (a árvore velha) e plantar um sistema novo do zero, você vai envolvendo o monolito com serviços novos. Cada pedço de funcionalidade que sai do monolito vira um microserviço ao redor dele. O tráfego é desviado pouco a pouco para os serviços novos. Quando o monolito não tem mais nada útil dentro dele, você o "desliga" — e os microserviços já estão sustentando tudo sozinhos.
A ideia central é nunca parar de entregar valor durante a migração. O sistema continua no ar, os clientes continuam usando, o time continua lançando features. A migração acontece nas bordas, em pedaços pequenos e reversíveis.
Regra de ouro: cada extração deve ser pequena o suficiente para ser revertida em minutos se algo der errado. Se uma extração não pode ser revertida, ela é grande demais.
Por que "reescrever tudo de uma vez" dá errado {#nao-reescreva}
A tentação clássica é: "nosso monolito está uma bagunça, vamos congelar as features por 6 meses e reescrever tudo limpo". Isso quase sempre termina mal. A história se repete:
- O monolito continua evoluindo: negócio não para. Enquanto o time novo reescreve, o monolito recebe features que a reescrita não conhece. No fim, a reescrita está sempre atrás.
- Big bang deployment: no dia de trocar, você descobre mil edge cases que só existiam em produção. Sem rollback fácil, vira madrugada de incidente.
- Ninguém aprende com o monolito: a reescrita repete erros que o monolito já tinha corrigido em produção há anos.
- ROI só no fim: 6 meses sem entrega de valor, e só no dia D você sabe se funcionou. Risco enorme de ter gastado muito em algo que não atende.
| Abordagem | Reescrita big-bang | Strangler Fig |
|---|---|---|
| Entrega de valor durante migração | Congelada | Contínua |
| Risco por etapa | Muito alto | Baixo (reversível) |
| Feedback do usuário | Só no fim | A cada extração |
| Acompanha evolução do negócio | Não | Sim |
| Tempo para primeiro microserviço em prod | Meses | Semanas |
| Rollback | Quase impossível | Por extração |
Como funciona, passo a passo {#passo-a-passo}
O fluxo do Strangler Fig é um ciclo que se repete para cada funcionalidade extraída:
Passo 1 — Identificar a funcionalidade candidata
Procure uma costura (seam) no monolito: uma parte com fronteiras razoavelmente claras, baixo acoplamento com o resto e que traria valor sendo independente. Bons candidatos:
- Funcionalidade com escala diferente do resto (ex.: busca vs. checkout).
- Domínio que muda com frequência e trava deploys do monolito todo.
- Integração com sistema externo que merece isolar.
- Funcionalidade que um novo time vai assumir.
Passo 2 — Colocar um "interceptador" na frente
Antes de mexer no monolito, coloque um ponto de roteamento entre clientes e o monolito. Pode ser um API Gateway, um proxy, ou até um pequeno "facade" dentro do próprio monolito. A função: redirecionar tráfego daquela funcionalidade para onde você quiser, sem que o cliente perceba.
Neste momento, 100% do tráfego ainda vai para o monolito. Você só criou a alavanca de roteamento.
Passo 3 — Construir o novo microserviço
Construa o serviço novo, com seu próprio banco (ou réplica de leitura), sua própria pipeline, seus próprios testes. Ele implementa só a funcionalidade escolhida.
Passo 4 — Desviar uma fração do tráfego
Roteie 1% (ou só um grupo interno, ou só um cliente piloto) para o novo serviço. Compare com o monolito: métricas, erros, latência, resultado de negócio. Se estiver igual ou melhor, aumente: 5% → 25% → 50% → 100%.
Passo 5 — Remover a funcionalidade do monolito
Quando 100% do tráfego está no novo serviço, apague o código antigo do monolito. Esse é o momento em que a figueira "estrangulou" aquele pedaço. A árvore velha ficou menor, o novo serviço ocupa aquele espaço.
Passo 6 — Repetir
Volte ao Passo 1 com outra funcionalidade. O ciclo se repete até o monolito virar uma casca vazia — ou até parar antes (veremos quando).
Cliente
│
[Gateway]
╱ │ ╲ ← roteamento por funcionalidade
╱ │ ╲
[MS-novo] [Monolito cada vez menor]
O Anti-Corruption Layer (ACL) {#acl}
Quando o novo microserviço precisa falar com o monolito (ou vice-versa), surge um perigo silencioso: o modelo bagunçado do monolito "vaza" para o serviço novo e o contamina. Você sai de um monolito sujo para microserviços sujos.
O Anti-Corruption Layer (camada anticorrupção) é uma tradução entre os dois mundos. Pense numa embaixada: você fala com o embaixador no seu idioma, ele fala com o país estrangeiro no idioma deles. Você nunca precisa aprender o idioma bagunçado do monolito, e o monolito nunca aprende o seu modelo limpo.
Na prática, o ACL é um pequeno componente (pode ser um serviço separado, um adapter, ou parte do gateway) que:
- Recebe chamadas no modelo novo (limpo, alinhado ao domínio).
- Traduz para o modelo legado (tabelas espalhadas, campos
campo_aux_2, lógica escondida em triggers). - Esconde as idiossincrasias do monolito do resto do mundo novo.
# ACL: traduz Pedido (modelo novo) para o formato que o monolito espera
class PedidoAcl:
def __init__(self, monolito_client):
self.monolito = monolito_client
def criar_pedido(self, pedido: Pedido) -> str:
# Modelo novo: limpo, legível
payload_legado = {
"TBL_PEDIDO": {
"PED_ID_CLI": pedido.cliente_id, # campo renomeado
"PED_VLR_TOT": pedido.total, # tipo ajustado
"PED_DT_INC": pedido.criado_em.strftime("%Y%m%d"), # formato antigo
"PED_STA": "I", # status inventado pelo legado
},
"TBL_PEDIDO_AUX": {
"AUX_CAMPO2": pedido.cupom or "", # campo genérico reutilizado
},
}
resposta = self.monolito.gravar(payload_legado)
return resposta["PED_ID"]
Sem o ACL, o cupom viraria AUX_CAMPO2 espalhado por todo o serviço novo —
a bagunça teria migrado. Com o ACL, a sujeira fica num lugar só, fácil de
jogar fora quando o monolito morrer.
Roteamento: API Gateway e feature flags {#roteamento}
O roteamento é o coração operacional do Strangler Fig. As duas ferramentas mais usadas:
API Gateway
Um gateway (Kong, Traefik, AWS API Gateway, Envoy) recebe todas as requisições e decide, por path, para onde mandar:
# Exemplo conceitual de rotas no gateway
routes:
- path: /api/checkout/* # extraído → vai para o novo serviço
upstream: checkout-service
- path: /api/* # resto → continua no monolito
upstream: monolito
Vantagem: roteamento centralizado, fácil de mudar, sem mexer no código do monolito. Quando a extração terminar, só remove a rota legada.
Feature flags para migração interna
Para funcionalidades dentro do mesmo backend, use feature flags (LaunchDarkly, Unleash, Flagsmith) para decidir por usuário/tenant se a chamada usa o código antigo ou o novo serviço:
def processar_pagamento(pedido):
if feature_flag.usar_servico_novo(pedido.cliente_id):
return pagamento_service_novo.cobrar(pedido) # microserviço
return gateway_monolito.cobrar(pedido) # legado
Assim você libera o novo serviço primeiro para clientes beta, compara comportamento e expande sem deploys arriscados.
Migração de banco de dados {#banco}
O banco é geralmente a parte mais difícil da migração. O monolito costuma ter um banco gigante e compartilhado, e os dados não se separam em domínios tão facilmente quanto o código. Estratégias, do mais simples ao mais avançado:
1. Banco compartilhado (não recomendado, mas realista)
O novo serviço acessa o mesmo banco do monolito, com permissão só de leitura em tabelas que precisa. Rápido para começar, mas quebra o isolamento — mudança de schema do monolito quebra o serviço novo. Use só como passo temporário.
2. Réplica de leitura via CDC
Use CDC (Change Data Capture — captura de mudanças de dados) com Debezium para replicar as tabelas relevantes do monolito para o banco do novo serviço. O serviço lê do seu próprio banco (rápido), e os dados se atualizam em segundos via stream de mudanças. Mantém isolamento sem mudar o monolito.
3. Sincronização por eventos
O monolito publica eventos ("pedido criado", "cliente atualizado") num broker (Kafka) e o novo serviço consome e monta sua própria visão dos dados. É o modelo mais limpo, mas exige que o monolito aprenda a publicar eventos — modificação necessária.
4. Separação completa (estado final)
O novo serviço é dono dos seus dados. O monolito deixa de escrever naquelas tabelas. Migração dos registros existentes feita em batch. Esta é a chegada do "serviço realmente independente".
| Estratégia | Esforço inicial | Isolamento | Quando usar |
|---|---|---|---|
| Banco compartilhado | Baixo | Nenhum | Primeira extração, ponte rápida |
| Réplica via CDC | Médio | Alto (leitura) | Serviço só lê dados do monolito |
| Sincronização por eventos | Médio-alto | Alto | Serviço precisa reagir a mudanças |
| Separação completa | Alto | Total | Estado final, dono dos dados |
Cuidado com joins cross-serviço: quando os dados estão em bancos diferentes, um
JOINtrivial vira uma dor. Planeje agregações via materialized views, desnormalização ou chamadas de API com cache.
Quando parar de migrar {#quando-parar}
Um erro comum é achar que o objetivo é "extrair tudo". Não é. O objetivo é entregar valor, e às vezes parar no meio é a decisão certa.
Pare de extrair quando:
- O que sobrou no monolito é estável e baixo risco: funcionalidades que não mudam há anos, com escala sem pressão, podem ficar no monolito para sempre. Um monolito pequeno e estável é ótimo.
- O custo da próxima extração supera o benefício: as últimas costuras são as mais difíceis (alto acoplamento). Se a extração custa 3 meses e libera pouco valor, mantenha no monolito.
- A equipe precisa entregar features: prioridade de negócio mudou. Pausar a migração não é fracasso, é pragmatismo.
- O monolito virou "serviço CRUD": quando só sobram telas simples e CRUDs, ele é praticamente um microserviço grande. Deixe quieto.
O Strangler Fig não é uma religião. É uma ferramenta. Use enquanto pagar.
Exemplo prático: extraindo o checkout {#exemplo}
Cenário: e-commerce com monolito em Rails. Checkout é o gargalo de escala (black friday) e trava deploys. Vamos extraí-lo.
Semana 1 — Roteamento Colocamos um API Gateway na frente do monolito. Tudo ainda aponta para o monolito. Métricas de baseline coletadas (latência, erro rate por path).
Semana 2-3 — Modelo e ACL
Definimos o modelo Pedido, Item, Pagamento do novo serviço. Criamos a
ACL que traduz para as tabelas pedidos, itens_ped, pgto do monolito.
Semana 4-6 — Novo serviço
Construímos checkout-service em Go, com seu próprio banco (Postgres). Usa
CDC do monolito para ter o catálogo de produtos e preços (réplica de leitura).
Semana 7 — Canary
Gateway roteia /api/checkout/* para o novo serviço em 1% do tráfego
(clientes marcados como beta). Comparação lado a lado:
# Config de canary no gateway
routes:
- path: /api/checkout/*
strategy: canary
canary:
upstream: checkout-service
weight: 1 # 1% para o novo
fallback: monolito
monitor:
error_rate_threshold: 0.5%
latency_p99_threshold: 800ms
auto_rollback: true
Semana 8-10 — Expansão Sem incidentes, subimos para 10% → 50% → 100%. Em 100%, o monolito para de receber checkout.
Semana 11 — Limpeza
Removemos o código de checkout do monolito. Apagamos as rotas legadas. O
monolito encolheu. O checkout-service deploya sozinho, escala sozinho, e
está pronto para a próxima black friday.
Total: ~11 semanas, com valor entregue em produção desde a semana 7, e reversível a qualquer momento até a semana 10.
Anti-padrões e armadilhas reais {#armadilhas}
"Distribuído demais"
Cada extração cria um novo serviço, novo banco, nova pipeline. Extrair 40 serviços de um monolito de 5 pessoas gera mais dor do que valor. A extração deve seguir fronteiras de domínio e de time, não fronteiras de classe.
"Banho de dados compartilhado disfarçado"
Serviços "separados" que compartilham o mesmo banco não são microserviços — são um monolito distribuído com latência de rede. O isolamento de dados é o que define o limite real.
"Síndrome do bolso-cheio de features"
Aproveitar a migração para "reformar" a funcionalidade — adicionar features novas, mudar regras de negócio, redesenhar UX. Isso quebra a comparabilidade entre o novo e o velho, dificultando o canary e o rollback. Primeiro migre comportamento igual. Só depois evolua.
"Sem observabilidade no novo serviço"
Colocar um serviço novo em produção sem tracing distribuído, métricas RED e logs estruturados é dirigir no escuro. Você não consegue comparar com o monolito, não detecta regressões, e o canary vira loteria.
"Dependência circular nova → velho → nova"
O serviço novo chama o monolito, que chama o serviço novo. Latência explode e debugging vira pesadelo. Quebre o ciclo: evento assíncrono, ou consolide a responsabilidade num único lado.
Erros comuns {#erros}
- Reescrever em vez de estrangular: parar o time por meses. Estrangule incrementalmente e entregue valor desde cedo.
- Sem gateway/proxy antes de começar: quando você precisa desviar tráfego, descobre que o cliente chama o monolito direto por IP. Coloque o roteamento antes de extrair qualquer coisa.
- Extração grande e irreversível: a primeira extração deve ser pequena e reversível para treinar o processo. Pegue uma funcionalidade de baixo risco primeiro.
- Ignorar o ACL: o modelo sujo do monolito contamina o serviço novo. Traduça sempre numa camada isolada.
- Misturar migração com evolução: mudar regras durante a migração quebra o canary. Mantenha o comportamento idêntico até 100%.
- Sem estratégia de dados: começar o serviço novo sem definir se vai compartilhar, replicar ou sincronizar dados. Decida o modelo de dados antes de escrever a primeira linha do serviço.
- Não comparar métricas novo vs. velho: rodar canary sem dashboard lado a lado. Você só descobre o problema quando o cliente reclama.
- Não remover o código velho: depois de 100% no novo, o código legado fica "por via das dúvidas". Vira dívida que ninguém entende mais. Remova.
- Extrair sem fronteira de time: um microserviço que ninguém é dono vira órfão. Só extraia se houver um time para assumi-lo.
- Achar que Strangler Fig é para tudo: algumas partes do monolito podem e devem ficar. Avalie custo/benefício de cada extração.
Checklist de migração {#checklist}
- Gateway/proxy na frente do monolito antes de qualquer extração
- Funcionalidade candidata tem fronteira de domínio e de time clara
- ACL definida para traduzir modelo novo ↔ legado
- Estratégia de dados escolhida (compartilhado, CDC, eventos, separação)
- Novo serviço com observabilidade (logs, métricas RED, tracing)
- Pipeline de CI/CD independente para o novo serviço
- Canary com comparação de métricas lado a lado e auto-rollback
- Comportamento idêntico ao legado durante a migração (sem features novas)
- Plano de remoção do código velho ao atingir 100%
- Documentação da decisão de parar (se parar antes do fim)
- Reversibilidade testada: simular rollback e medir tempo de recuperação
Migrando do monolito para microserviços? A Inicialize Tec conduz migrações incrementais com Strangler Fig — da escolha das primeiras costuras ao roteamento, ACL e estratégia de dados, sem parar suas entregas. Conversamos sobre seu caso.