Como aplicar o padrão strangler fig em migrações graduais

1. Introdução ao padrão Strangler Fig

O padrão Strangler Fig (figueira-estranguladora) tem sua origem na natureza: uma espécie de figueira que cresce ao redor de uma árvore hospedeira, gradualmente substituindo seu sistema de suporte até que a árvore original morra, deixando apenas a figueira em seu lugar. Na engenharia de software, esse padrão descreve uma estratégia de migração incremental onde um sistema legado é progressivamente substituído por um novo sistema, sem interrupção do serviço.

O problema central que o padrão resolve é a migração de sistemas legados complexos, onde uma refatoração total (big bang) apresenta riscos elevados de downtime, perda de dados e falhas catastróficas. Diferentemente de uma reescrita completa, o Strangler Fig permite que funcionalidades sejam substituídas uma a uma, mantendo o sistema legado operacional até que todas as peças estejam no lugar.

2. Princípios fundamentais do padrão

Três pilares sustentam o padrão Strangler Fig:

Separação de preocupações: O sistema antigo e o novo coexistem de forma independente, com interfaces bem definidas entre eles. Cada funcionalidade migrada é encapsulada no novo sistema, sem dependências ocultas com o legado.

Roteamento gradual de funcionalidades: Um mecanismo de roteamento (geralmente um proxy ou API Gateway) decide se uma requisição deve ser processada pelo sistema antigo ou pelo novo. Esse roteamento pode ser alterado dinamicamente, permitindo testes controlados.

Substituição sem impacto no usuário final: O usuário percebe zero mudanças no comportamento ou na disponibilidade do sistema. A migração é transparente, ocorrendo nos bastidores.

3. Etapas iniciais: Análise e planejamento

Antes de escrever uma linha de código, é essencial mapear o sistema legado. Crie um inventário detalhado de todas as funcionalidades, endpoints, dependências externas e fluxos de dados. Identifique quais módulos são mais independentes e de baixo risco para começar a migração.

# Exemplo de mapeamento de funcionalidades
Funcionalidade: Autenticação de usuários
  - Dependências: Banco de dados de usuários, serviço de e-mail
  - Risco: Alto (impacto em todo o sistema)
  - Prioridade: 3 (último a migrar)

Funcionalidade: Listagem de produtos
  - Dependências: Catálogo de produtos (apenas leitura)
  - Risco: Baixo (sem efeitos colaterais)
  - Prioridade: 1 (primeiro a migrar)

A arquitetura de transição deve incluir um proxy reverso ou API Gateway que intercepte todas as requisições e decida o destino. Ferramentas como NGINX, Kong ou AWS API Gateway são comuns nesse papel.

4. Implementação da camada de interceptação

O coração do Strangler Fig é a camada de interceptação. Ela deve ser capaz de rotear tráfego com base em regras flexíveis:

  • Por URL: /api/v1/ → sistema antigo, /api/v2/ → sistema novo
  • Por funcionalidade: requisições de "listar produtos" vão para o novo sistema
  • Por dados: usuários com ID par usam o novo sistema (testes A/B)
# Configuração de proxy reverso (NGINX)
server {
    listen 80;

    location /api/v2/ {
        proxy_pass http://novo-sistema:3000/;
    }

    location /api/ {
        proxy_pass http://sistema-legado:8080/;
    }

    # Roteamento por funcionalidade específica
    location = /api/produtos {
        proxy_pass http://novo-sistema:3000/produtos;
    }
}

O tratamento de estados compartilhados é crítico. Se o sistema legado mantém sessões em memória, o novo sistema deve acessar o mesmo repositório de sessões (ex: Redis) ou implementar um mecanismo de migração de sessão.

5. Migração incremental de funcionalidades

Comece com funcionalidades de baixo risco e sem dependências complexas. A estratégia típica é:

  1. Implementar a funcionalidade no novo sistema
  2. Roteá-la para o novo sistema (com fallback para o legado)
  3. Validar o comportamento (testes A/B, monitoramento)
  4. Remover o fallback após confirmação
# Exemplo de código para fallback com timeout
function buscarProduto(id, req) {
  const novoResultado = await tentarNovoSistema(id);
  if (novoResultado.sucesso) {
    return novoResultado.dados;
  }

  // Fallback para sistema legado
  console.warn(`Fallback para legado no produto ${id}`);
  return await buscarNoLegado(id);
}

A sincronização de dados entre sistemas pode ser feita via replicação assíncrona (fila de mensagens) ou sincronização em lote durante janelas de manutenção.

6. Gerenciamento de dívida técnica e riscos

Riscos comuns incluem inconsistência de dados, aumento de latência e funcionalidades acopladas. Estratégias de mitigação:

  • Monitoramento: Implemente dashboards que comparem resultados do sistema antigo e novo
  • Rollback: Mantenha a capacidade de reverter o roteamento para o legado em minutos
  • Funcionalidades acopladas: Use feature flags para desativar funcionalidades problemáticas sem afetar o restante
# Exemplo de feature flag para rollback rápido
const FUNCIONALIDADES_MIGRADAS = {
  'listarProdutos': true,
  'buscarUsuario': false,  // Problema identificado, mantendo legado
};

function deveUsarNovoSistema(funcionalidade) {
  return FUNCIONALIDADES_MIGRADAS[funcionalidade] || false;
}

7. Finalização e remoção do sistema legado

Quando todas as funcionalidades estiverem migradas e validadas, é hora de descomissionar o sistema legado. Esse processo deve ser gradual:

  1. Redirecione 100% do tráfego para o novo sistema
  2. Mantenha o legado em modo somente leitura por um período
  3. Remova o proxy de roteamento
  4. Desligue os servidores legados

Documente cada etapa e registre lições aprendidas para futuras migrações.

8. Estudo de caso e boas práticas

Cenário: Migração de um monolito Java para microsserviços Node.js em uma plataforma de e-commerce.

Etapas:
1. Catálogo de produtos foi migrado primeiro (baixo risco, sem transações)
2. Carrinho de compras foi migrado com replicação de dados via RabbitMQ
3. Autenticação foi migrada por último, após validação completa

Erros comuns e soluções:
- Erro: Tentar migrar funcionalidades muito acopladas de uma vez
- Solução: Quebrar em subfuncionalidades menores
- Erro: Não ter rollback testado antes da migração
- Solução: Testar rollback em ambiente de staging
- Erro: Ignorar a latência adicional do proxy
- Solução: Implementar cache no proxy para funcionalidades críticas

Métricas de sucesso:
- Zero downtime durante toda a migração (6 meses)
- 99.9% de consistência de dados entre sistemas
- Tempo de resposta 40% menor após migração completa

Referências