Índice

  1. O que é o padrão Strangler Fig
  2. Por que "reescrever tudo de uma vez" dá errado
  3. Como funciona, passo a passo
  4. O Anti-Corruption Layer (ACL)
  5. Roteamento: API Gateway e feature flags
  6. Migração de banco de dados
  7. Quando parar de migrar
  8. Exemplo prático: extraindo o checkout
  9. Anti-padrões e armadilhas reais
  10. Erros comuns
  11. 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:

  1. 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.
  2. 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.
  3. Ninguém aprende com o monolito: a reescrita repete erros que o monolito já tinha corrigido em produção há anos.
  4. 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.
AbordagemReescrita big-bangStrangler Fig
Entrega de valor durante migraçãoCongeladaContínua
Risco por etapaMuito altoBaixo (reversível)
Feedback do usuárioSó no fimA cada extração
Acompanha evolução do negócioNãoSim
Tempo para primeiro microserviço em prodMesesSemanas
RollbackQuase impossívelPor 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 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égiaEsforço inicialIsolamentoQuando usar
Banco compartilhadoBaixoNenhumPrimeira extração, ponte rápida
Réplica via CDCMédioAlto (leitura)Serviço só lê dados do monolito
Sincronização por eventosMédio-altoAltoServiço precisa reagir a mudanças
Separação completaAltoTotalEstado final, dono dos dados

Cuidado com joins cross-serviço: quando os dados estão em bancos diferentes, um JOIN trivial 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}

  1. Reescrever em vez de estrangular: parar o time por meses. Estrangule incrementalmente e entregue valor desde cedo.
  2. 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.
  3. 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.
  4. Ignorar o ACL: o modelo sujo do monolito contamina o serviço novo. Traduça sempre numa camada isolada.
  5. Misturar migração com evolução: mudar regras durante a migração quebra o canary. Mantenha o comportamento idêntico até 100%.
  6. 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.
  7. Não comparar métricas novo vs. velho: rodar canary sem dashboard lado a lado. Você só descobre o problema quando o cliente reclama.
  8. 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.
  9. Extrair sem fronteira de time: um microserviço que ninguém é dono vira órfão. Só extraia se houver um time para assumi-lo.
  10. 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.