01 - Strangler Fig Pattern (Migração gradual)
video: https://youtu.be/... no frontmatter da nota.Strangler Fig Pattern (Migração gradual)
O Strangler Fig Pattern recebe o nome da figueira estranguladora, uma planta que germina sobre uma árvore hospedeira, cresce ao redor dela e, com o tempo, a substitui por completo até restar apenas a figueira. Em software, o padrão descreve a substituição incremental de um sistema legado por novos serviços construídos ao redor dele, sem nunca parar a operação. Uma fachada é colocada na frente do legado e passa a rotear cada requisição para o destino correto conforme as capacidades de negócio são movidas. O movimento é feito por fatias verticais: uma capacidade como "emitir fatura" migra inteira, com sua API, seus dados e seu time, antes de a próxima começar. É o contraponto direto ao rewrite em big bang, que concentra risco técnico e de prazo em uma entrega única de meses. Em produção, o padrão é a estratégia padrão de mercado para modernizar monolitos em nuvem com risco controlado e valor entregue continuamente.
Conceito e metáfora
A figueira estranguladora não derruba a árvore hospedeira de uma vez; ela lança raízes para o solo, ganha fonte própria de nutrientes e cresce até que a hospedeira morre por falta de função, não por ataque direto. O análogo no sistema é exato: o legado continua rodando e atendendo tráfego real enquanto as novas raízes, os serviços modernos, assumem capacidades uma a uma. Em nenhum momento existe um "dia do corte" em que tudo muda junto.
Três papéis compõem a estrutura. O sistema legado mantém tudo que ainda não migrou e continua em produção durante toda a transição. A fachada, que pode ser um balanceador, um API gateway ou um proxy reverso, é o único ponto de entrada visível ao cliente e decide, por rota ou por flag, quem atende cada chamada. Os novos serviços nascem com tecnologia alvo, geralmente serverless ou contêineres, e donos próprios, sem herdar amarras do código antigo.
- Fachada: ponto único de entrada que oculta a coexistência de legado e novos serviços do ponto de vista do cliente.
- Capacidade de negócio: fatia vertical de valor (exemplo: cobrança, catálogo, notificação) que migra como unidade inteira.
- Roteamento gradual: desvio progressivo de tráfego por rota, por header, por percentual ou por feature flag.
- Descomissionamento: aposentadoria formal do legado quando a última capacidade sai dele e o fluxo de dados cessa.
O problema que resolve
Reescrever um sistema inteiro de uma vez falha com frequência documentada: o negócio não para, os requisitos mudaram desde que o legado foi escrito, e o comportamento real só se descobre em produção. A equipe nova passa meses reproduzindo bugs antigos sem entregar valor, e o projeto é cancelado no meio, deixando dois sistemas metade prontos. O Strangler Fig ataca essa raiz ao nunca congelar o legado e ao entregar cada fatia migrada em produção, com valor real e aprendizado real por entrega.
O padrão resolve ainda o problema de risco acumulado. Como cada migração é pequena, revertível e observável, o custo de um erro é o de uma capacidade, não o do sistema inteiro. Se a nova implementação de faturamento falhar, a fachada volta a rotear para o legado em minutos. Essa propriedade de reversão transforma uma aposta de anos em uma sequência de apostas pequenas, o que muda a conversa com patrocinadores executivos de fé para evidência.
Funcionamento interno
A mecânica começa com a fachada interceptando 100% do tráfego, ainda que encaminhe tudo ao legado. Esse passo parece trivial e não é: estabelecer a fachada como ponto único de entrada é o pré-requisito para qualquer desvio futuro, e descobrir rotas mal mapeadas nesse estágio é barato. Em nuvem, a fachada costuma ser um ALB com listener rules, um API Gateway com mapeamentos por recurso ou um ingress do Kubernetes com anotações de peso.
Depois, cada capacidade migra por um ciclo repetido: implementar a nova API, espelhar o comportamento do legado, testar com tráfego real desviado por percentual, subir o percentual conforme as métricas comprovam equivalência e, por fim, congelar a rota no novo serviço. Durante a coexistência, é comum usar shadow traffic, em que a nova implementação recebe cópias das requisições sem responder ao cliente, para comparar respostas em produção sem risco.
# Plano de migração strangler: capacidades, rotas e critérios de saída
legado: monolito-jee-faturamento
fachada:
tipo: alb # listener rules encaminham por path
dns: faturamento.interno.empresa.br
fases:
- capacidade: consultas de faturas
rota: "/faturas/*"
destino: servico-faturas (Lambda + DynamoDB)
estrategia: roteamento por rota, 5% -> 25% -> 100%
criterio_saida: 30 dias com erro < 0.1% e latência p99 menor que o legado
- capacidade: emissão de notas fiscais
rota: "/notas/*"
destino: servico-notas (ECS Fargate)
estrategia: feature flag por tenant, migração cliente por cliente
criterio_saida: todos os tenants com flag ativa e 60 dias estáveis
- capacidade: conciliação contábil
rota: "*"
destino: monolito (restante do legado)
estrategia: mantida no legado até a última fase
O roteamento por feature flag complementa o roteamento por rota quando a granularidade necessária é por cliente, por tenant ou por experimento, e não por path:
# Fachada em nginx: roteamento gradual por rota com percentual por header
location /clientes/ {
proxy_pass http://clientes-novo.internal; # 100% já no novo serviço
}
location /faturas/ {
# 20% do tráfego no novo; split controlado por weight no upstream
split_clients "${remote_addr}" $faturas_upstream {
20% faturas-v2.internal;
* monolito-legado.internal;
}
proxy_pass http://$faturas_upstream;
}
location / {
proxy_pass http://monolito-legado.internal; # resto segue no legado
}
Aplicação prática em nuvem
Em AWS, o arranjo típico usa CloudFront ou ALB como fachada, com listener rules por path encaminhando para target groups distintos, um para o legado em EC2 ou ECS e outro para os novos serviços. API Gateway é a alternativa quando o tráfego é REST e o time quer autenticação, throttling e mapeamento por estágio sem operar proxy. O Application Load Balancer tem vantagem operacional em migrações de longo prazo: listener rules suportam peso por target group, o que permite split percentual nativo entre legado e novo serviço com uma alteração de infraestrutura como código.
O rastreamento da transição exige um identificador comum atravessando fachada, novo serviço e legado. Um header como X-Request-ID propagado da fachada para os dois lados permite reconstruir a jornada completa no sistema de tracing e comparar, requisição a requisição, o comportamento das duas implementações. Sem essa ponte, a comparação de métricas entre legado e novo se reduz a médias agregadas, que escondem exatamente os casos de borda que importam.
- Stage 0: fachada intercepta tudo e roteia 100% ao legado; inventário de capacidades e rotas concluído e versionado.
- Stage 1: primeira capacidade migra em canário de 5%, com comparador de respostas rodando em shadow traffic.
- Stage 2: capacidades centrais migram por tenant com feature flag, e o dado passa por posse gradual via CDC.
- Stage 3: legado entra em modo manutenção, recebendo apenas leituras residuais, e a fachada concentra 100% no novo.
- Stage 4: descomissionamento formal, com backup do legado, revogação de acessos e encerramento de custos.
Migração de dados
O dado é a parte mais difícil e separa projetos bem sucedidos de cemitérios de serviços órfãos. Enquanto uma capacidade coexiste com o legado, ambos precisam ler e escrever dados consistentes, e a estratégia depende do acoplamento. A abordagem mais comum é mover a posse da tabela para o novo serviço e expor os dados ao legado via API ou sincronização, invertendo a dependência em vez de compartilhar o schema.
- Change Data Capture: o novo serviço consome eventos de mudança do banco do legado e popula seu próprio armazenamento, assumindo a leitura antes da escrita.
- Posse gradual da escrita: primeiro o novo serviço lê do próprio store sincronizado; depois assume escritas com o legado passando a consumir eventos; por fim o dado legado vira histórico.
- Compatibilidade temporária: views e adapters no banco legado leem do novo store durante a janela de coexistência, evitando ETL manual e divergência silenciosa.
- Cortes de fila e job agendado: rotinas assíncronas do legado que escrevem na capacidade migrada precisam ser redirecionadas na mesma fase, ou a divergência reaparece à noite.
Comparações
O padrão compete com três alternativas, e a escolha depende de prazo, risco e capacidade do time de manter dois sistemas vivos simultaneamente.
| Critério | Strangler Fig | Big Bang Rewrite | Branch by Abstraction |
|---|---|---|---|
| Risco por entrega | Baixo, fatia pequena | Alto, tudo de uma vez | Médio, por merge |
| Tempo até primeiro valor | Dias ou semanas | Meses até o corte | Semanas |
| Reversibilidade | Por capacidade | Praticamente nula | Por release |
| Requisito principal | Fachada e roteamento | Congelamento do legado | Refatorar antes de migrar |
| Risco de paralelização | Dois sistemas vivos | Um só até o corte | Um só, evolução interna |
Vale distinguir o Strangler Fig da Anti-Corruption Layer: a ACL é um padrão tático de tradução entre modelos de domínio, e costuma ser um componente interno do Strangler Fig, garantindo que o modelo novo não contamine o modelo legado nem vice-versa. Um strangler sem ACL tende a reproduzir as entorses do legado nos novos serviços.
A escolha também depende de quem opera durante a transição. O Strangler exige que o time domine roteamento, observabilidade distribuída e sincronização de dados ao mesmo tempo em que entrega features, e isso precisa estar no plano de capacidade do time. Se a equipe é pequena demais para operar dois sistemas, a alternativa honesta é reduzir o escopo da migração em vez de trocar o padrão.
Riscos e erros comuns
- Fachada que vira monolito: regras de negócio começam a ser escritas na camada de roteamento, que deveria ser burra; a correção é manter nela apenas roteamento, autenticação de borda e telemetria.
- Migração por camada técnica: mover "todo o acesso a dados" primeiro em vez de capacidades verticais inteiras, o que deixa o sistema metade migrado por anos sem valor de negócio.
- Dados compartilhados sem dono: dois serviços escrevendo no mesmo banco durante coexistência indefinida; o dado precisa ganhar dono e contrato no início da fase, não no fim.
- Fog of war: serviços novos e antigos coexistindo tanto tempo que ninguém sabe mais quem atende o quê; o plano precisa ter data de morte para o legado desde o dia um.
- Ignorar o negativo: capacidades mortas do legado migram junto por falta de análise, transportando dívida que já deveria ser aposentada.
- Observabilidade partida: métricas e traces sem identificador comum entre legado e novo serviço, o que impede comparar as duas implementações com honestidade.
- Data de corte sem contrato: prometer encerrar o legado sem acordo com o negócio sobre janelas de transição, o que congela a coexistência no meio do caminho.
Boas práticas em produção
Trate cada migração como um experimento com hipótese mensurável: comparar latência, taxa de erro e custo por requisição entre as duas implementações antes de trocar a rota permanentemente. Mantenha um inventário vivo das capacidades do legado, com status e dono, pois ele é o mapa real do strangler; sem ele, o progresso vira opinião. Automatize o desvio de tráfego por pipeline com feature flags versionadas, nunca por mudança manual na fachada.
Defina contracts tests entre fachada e serviços para garantir que a nova implementação honre o comportamento que o cliente já consome, e rode esses testes no CI contra mocks gerados do tráfego real. Publique o status da migração em um dashboard com percentual por capacidade, porque visibilidade pública cria a pressão saudável que evita o estado permanente de coexistência.
Por fim, celebre o descomissionamento tanto quanto o lançamento: desligar servidores do legado, revogar acessos e arquivar o banco é a única prova de que o padrão terminou. Um strangler bem executado termina com custo menor, time mais veloz e um legado aposentado com honras, e não com uma figueira eternamente pendurada em uma árvore que nunca morre.