Home Comunidade Eventos YouTube Networks Desafios // produtos Academy News CTI | CVE Contato

05 - Migração de Monolito para Microsserviços

#anti-corruption#microsservicos#migracao#monolito#strangler-fig
video explicativo
Video explicativoEspaco reservado para o video desta anotação — adicione video: https://youtu.be/... no frontmatter da nota.

Migração de Monolito para Microsserviços

Este caso acompanha um ERP de logística construído como monolito Java de 1,2 milhão de linhas e 9 anos de idade, com 120 desenvolvedores em 8 times, deploy a cada 3 semanas em janela de 4 horas e rollback em 30% dos releases. As motivações são concretas e medidas: lead time de 45 dias entre commit e produção, acoplamento de escala em que o módulo de cotação força a escala do sistema inteiro, e conflito de merge crônico entre times que trava a entrega. Os objetivos arquiteturais são desacoplar releases e escala sem interromper a entrega de features, com proibição expressa de big bang. O desenho segue o padrão strangler fig: um roteador na frente, rotas migradas uma a uma e o monolito vivo durante toda a transição. A migração inteira tem orçamento e cronograma de 24 meses, com métricas de sucesso definidas antes da primeira extração.

Requisitos e pressupostos

As restrições do negócio definem o formato da migração:

  • Zero downtime: clientes são operações de logística com uso contínuo, e nenhuma janela de indisponibilidade é aceitável.
  • O monolito continua recebendo features por 2 anos: migração não é congelamento, e os times de produto não podem parar.
  • Time de plataforma com 4 pessoas: qualquer proposta que exija plataforma grande é inviável.
  • Comportamento equivalente verificado: nenhuma extração vai sem testes de paridade que comparem resposta antiga e nova com tráfego real.
  • Orçamento sem big bang: a migração compete com roadmap de produto por capacidade de time.
  • Rastreabilidade financeira durante a transição: nenhum pedido fica sem dono claro entre monolito e serviço novo em nenhum momento.

O dimensionamento do programa parte dos números do legado. O monolito atende 4 milhões de requisições por dia com picos de 800 por segundo na meia-noite, quando as cotações das transportadoras são recalculadas; o módulo de cotação responde por 35% dessas chamadas e escala em 20 vezes o próprio pico. Um time médio espera 45 dias para colocar uma mudança em produção, e 30% dos releases retornam por defeito, números que o programa usa como linha de base.

Os times foram reorganizados conforme as extrações, e não antes. Cada serviço extraído passa ao time que o usava no monolito, com dono único, e a plataforma serve os 8 times sem hierarquia central. O critério de sucesso organizacional é que nenhum time espere outro time para lançar, e o lead time por time aparece no mesmo painel das métricas técnicas.

Os pressupostos partem do diagnóstico honesto do legado. Os domínios já estão mapeados por uma iniciativa DDD anterior, o que poupou o projeto de refazer a modelagem. O banco compartilhado com 900 tabelas é o nó central do acoplamento, e nenhum plano que não enfrente os dados funciona. Os contratos internos do monolito são instáveis nos módulos jovens, então extração só faz sentido onde o contrato está estável ou pode ser estabilizado. A auditoria exige rastreabilidade completa durante a transição, com pedidos que atravessam o roteador identificáveis ponta a ponta.

Arquitetura proposta: strangler fig na prática

A peça central é um roteador na frente do monolito, com rotas migrando uma a uma sem mudança no cliente. O monolito permanece dono do banco durante a transição, e cada serviço extraído ganha banco próprio por caminho padronizado. Uma anti-corruption layer na fronteira traduz o modelo legado para o modelo de domínio novo, impedindo que decisões de 9 anos atrás contaminem o serviço novo. Para chamadas em sentido inverso, quando o monolito precisa do serviço novo, uma façade interna isola o ponto de integração. A configuração do roteador mostra a mecânica:

# Roteador na frente: rotas migradas saem do monolito sem mudar o cliente
upstream monolito { server monolito:8080; }
upstream cotacao { server cotacao:8080; }
upstream notificacoes { server notificacoes:8080; }

server {
  # rotas extraídas: cada uma pode voltar ao monolito em um rollback de rota
  location /api/cotacao/    { proxy_pass http://cotacao; }
  location /api/notificacoes/ { proxy_pass http://notificacoes; }

  # tudo que ainda não migrou segue para o monolito, sem exceção
  location /                { proxy_pass http://monolito; }
}

A sincronização de dados durante a transição é por captura de mudanças: enquanto um serviço novo usa tabelas ainda do monolito, o CDC espelha os dados para o banco próprio com verificação de paridade. A integração entre serviços extraídos é preferencialmente por eventos, publicados em Kafka, e chamadas síncronas são exceção com justificativa registrada. O roteador registra todas as rotas com versão, e o rollback de uma extração é voltar uma rota, operação de minutos que foi ensaiada em todas as fases.

A alternativa descartada foi a reescrita paralela, com sistema novo construído ao lado e cutover único. Dois projetos anteriores na mesma empresa falharam exatamente aí: o legado continuou crescendo durante a reescrita, e o alvo ficava para trás. O strangler fig aceita conviver com o legado em troca de entregas incrementais, e cada rota migrada vale por si só.

A observabilidade foi tratada como requisito de migração, e não de operação. Cada requisição atravessa o roteador com identificador de correlação, os serviços publicam traces no mesmo formato do monolito instrumentado, e o painel único mostra o percurso de um pedido cruzando monolito e serviços. Sem esse mapa, decidir o que extrair depois seria chute, porque os acoplamentos reais só aparecem nos traces de produção.

Os eventos seguem o vocabulário dos domínios extraídos: CotaCalculada, NotificacaoEnviada, ExpedicaoSeparada e FaturamentoFechado. O monolito publica os próprios fatos por outbox, e os serviços novos reagem sem chamada direta, o que reduz os pontos de sincronização durante a transição. Chamada síncrona entre monolito e serviço existe apenas na façada interna, para os casos em que o fluxo legado precisa de resposta imediata.

Os contratos entre monolito e serviços são versionados e gerados a partir do esquema, o que impede quebras silenciosas. Cada rota do roteador aponta uma versão, e a compatibilidade é verificada no pipeline antes do deploy, com teste de contrato contra o ambiente de produção em modo leitura. A façada interna concentra as chamadas em sentido inverso: quando o fluxo legado de faturamento precisa da cotação nova, chama a façada com timeout de 200 ms e fallback para o cálculo antigo em cache, e o volume diário dessas chamadas é métrica de desacoplamento, quanto menor, mais perto do fim da fase.

Fluxo principal: extração do primeiro serviço

A escolha do primeiro serviço define a reputação do programa inteiro. Os critérios aplicados foram quatro: baixo acoplamento de dados, alto valor para o negócio, dono claro entre os times e frequência de mudança que justifique o investimento. O primeiro extraído foi notificações, de baixo risco e baixo valor relativo, escolhido de propósito para ensinar o caminho com risco controlado; o segundo foi cotação, de alto valor, que resolveu o acoplamento de escala e provou o retorno. Nenhum serviço foi extraído por moda técnica: cada um entrou no cronograma por custo de coordenar mudanças no módulo legado.

O banco compartilhado é o nó do problema, e o caminho de separação segue quatro passos padrão. Primeiro, a lógica de acesso vira API interna do monolito, e o serviço novo nunca lê tabela direto. Segundo, o serviço novo consome essa API enquanto o comportamento estabiliza. Terceiro, o CDC espelha as tabelas relevantes para o banco próprio com conciliador diário de paridade. Quarto, o cutover de escrita acontece por roteador com reversão imediata disponível, e a verificação de paridade roda com tráfego sombra antes do corte.

O cutover de escrita é o momento mais tenso e o mais instrumentado. O conciliador compara linha a linha as tabelas espelhadas durante toda a fase de CDC, com divergência acumulada publicada no painel, e o corte só entra quando o contador está em zero por 3 dias consecutivos. Depois do corte, o monolito lê os dados pelo serviço novo pela façada, e as tabelas originais entram em modo leitura por 60 dias antes do desligamento.

Os números das primeiras extrações mostram o ritmo. Notificações levou 6 semanas do primeiro commit ao 100% do tráfego, com 2 semanas de sombra e nenhuma reversão; a equipe de 4 pessoas aprendeu o pipeline, a ACL e o CDC nesse módulo. Cotação levou 14 semanas, com 3 reversões de rota durante a paridade, porque o módulo guardava 9 anos de regras acumuladas em 40 tabelas; o banco próprio ficou com 12 tabelas, e o resto morreu por falta de uso.

# Anti-corruption layer: o modelo legado não vaza para o serviço novo
class CotacaoACL:
    def para_dominio(self, dto_legado):
        # legado guarda preço em centavos com imposto embutido; o domínio separa
        return Cotacao(
            id=IdLegado.para_uuid(dto_legado["codCot"]),
            valor_bruto=Centavos(dto_legado["vlrTot"]) - Imposto.extrair(dto_legado),
            # formato de data próprio de 1998, mantido longe do domínio novo
            validade=Converter.legado_para_iso(dto_legado["dtVal"]),
        )

    def para_legado(self, cotacao):
        # a saída reconstrói o contrato que o monolito entende, sem exceção
        return {"codCot": IdLegado.para_codigo(cotacao.id),
                "vlrTot": cotacao.valor_total_com_imposto().centavos,
                "dtVal": Converter.iso_para_legado(cotacao.validade)}

A ACL é escrita com testes de tradução exaustivos, porque ela é o único lugar onde os dois modelos se tocam. Cada campo do contrato legado tem dono no domínio novo, e campos sem tradução clara são sinal de que o corte de domínio está errado, e não de que a ACL precisa de exceção.

Decisões e trade-offs

Fase Duração Escopo Critério de saída
0. Fundações 3 meses Plataforma, roteador, observabilidade e CI/CD por serviço Deploy diário sem janela em ambiente de produção
1. Primeiras extrações 4 meses Notificações e cotação com banco próprio 5% do tráfego em serviços, paridade verificada
2. Aceleração 8 meses Expedição, rastreamento e faturamento 40% do tráfego, lead time abaixo de 20 dias
3. Fronteira de dados 5 meses Últimas tabelas compartilhadas e desligamento de acessos Nenhum acesso direto entre serviços e banco alheio
4. Consolidação 4 meses Desativação de módulos do monolito e revisão de custo Monolito reduzido a 40% das linhas originais

A leitura da tabela de fases mostra o desenho do risco. As duas primeiras fases gastam 7 meses em fundações e exemplos, e é o período que a pressão do negócio costuma atacar; cortá-lo repete o erro dos projetos de reescrita que o programa descartou. As fases centrais acumulam valor visível mês a mês, e a última fase é manutenção, e não construção.

As métricas de sucesso foram definidas antes da primeira extração e medidas por fase. O lead time caiu de 45 para 12 dias na fase 2, e a frequência de deploy subiu de 1 por mês para 40 por mês somando os serviços, com o monolito mantendo seu ritmo próprio. O MTTR caiu de 4 horas para 25 minutos, porque o rollback por roteador elimina a necessidade de reimplantar o sistema inteiro. O número de serviços nunca foi métrica: a régua é o fluxo de valor, e o programa seria bem-sucedido mesmo com menos extrações se as métricas atingissem a meta.

Métrica Antes Meta na fase 2 Medido no fim
Lead time de feature 45 dias 20 dias 12 dias
Frequência de deploy 1 por mês 20 por mês 40 por mês
MTTR 4 horas 1 hora 25 minutos
Tráfego em serviços novos 0 40% 55%
Rollback de release 30% 10% 6%

O cronograma de 24 meses foi defendido com duas simulações. Comprimir para 12 meses exigiria dobrar o time de plataforma e congelar features, custo rejeitado pelo negócio; esticar para 36 meses desaceleraria na metade, quando a motivação inicial esfria. O formato por fases com critério de saída permite pausar sem perder o caminho, porque cada fase termina com o sistema inteiro operável.

O custo de rodar em paralelo é o preço aceito do strangler fig. Durante a fase 3, os mesmos dados vivem em dois bancos com CDC e conciliador, e o custo de infraestrutura subiu 18% no pico da sobreposição, folga reservada no orçamento desde o início. O retorno veio no mesmo período, porque a extração da cotação derrubou o custo de escala do monolito em 25%, e a curva cruzou antes do fim do programa.

Escalabilidade e falhas: riscos e armadilhas

A armadilha número um é o monolito distribuído: extrair módulos mantendo chamadas síncronas em cadeia recria o acoplamento com latência pior e falha parcial nova. A mitigação foi regra explícita: profundidade máxima de duas chamadas síncronas entre serviços, integração preferencial por eventos, e revisão arquitetural obrigatória quando a regra precisa de exceção. O segundo risco é o banco compartilhado remanescente, que vira dependência eterna quando um serviço novo lê direto as tabelas do legado; a regra é acesso só por API do dono ou por CDC, e o conciliador aponta violação.

Outras armadilhas merecem registro. A paridade obsessiva, extrair módulo replicando 100% dos recursos, incluindo os abandonados, foi evitada por inventário de uso antes de cada extração, e 15% das features candidatas foram descartadas por falta de uso. Times sem plataforma produziriam 8 formas diferentes de fazer deploy, e o golden path da plataforma desde a fase 0 impediu o custo composto. A escala heterogênea, um dos motivos da migração, foi verificada na prática: a cotação escala 20 vezes no pico da meia-noite, e pela primeira vez a escala dele não arrasta o faturamento junto.

As falhas da migração têm protocolo. Toda extração entra com sombra de tráfego por 2 semanas, comparação de resposta e reversão de rota ensaiada; no programa inteiro, 3 extrações foram revertidas e refeitas, e nenhuma virou incidente de cliente. O conciliador diário de dados roda durante toda a fase 3, e qualquer divergência acima de zero trava o cutover daquele domínio até a causa ser eliminada.

Um incidente ilustra o valor das rotas reversíveis. Na fase 2, a extração do faturamento introduziu divergência de arredondamento percebida por um cliente grande; a rota voltou ao monolito em 4 minutos, o corretivo levou 3 dias, e a segunda entrada passou com sombra estendida para 4 semanas. O episódio entrou no registro do programa como argumento pelo critério de saída rígido de paridade.

O golden path da plataforma encurta o caminho de cada extração. Template de serviço com pipeline, observabilidade, segredos e padronização de segurança prontos reduz a criação de um serviço novo de 3 semanas para 2 dias, e o time de 4 pessoas sustenta os 8 times com esse molde. O custo é disciplina: desvios do molde precisam de justificativa, e a plataforma é dona do template, não dos serviços.

Cada serviço extraído herda deploy canário pelo pipeline da plataforma: 5% do tráfego por 30 minutos com comparação automática de taxas de erro e latência, seguido de rampa até 100%. O monolito mantém seu deploy tradicional durante a transição, e as duas cadências convivem no mesmo roteador sem conflito, porque as rotas isolam o destino.

O custo é monitorado por serviço desde a fase 1, com tag de alocação por domínio, e a revisão trimestral compara o custo por rota migrada contra o custo estimado no monolito. O desvio mais comum é o over-provisioning defensivo dos serviços novos, corrigido pelo uso real após 60 dias de métricas.

Lições aplicáveis

  • Extraia primeiro o que ensina, não o que impressiona: notificações, de baixo risco, ensinou o caminho completo que depois carregou cotação, de alto valor.
  • O código se separa em semanas, os dados em meses: o banco compartilhado é o último laço a romper, e o cronograma precisa refletir essa assimetria.
  • A anti-corruption layer é seguro, e não luxo: cada exceção permitida ao modelo legado no serviço novo virou dívida documentada e cara.
  • Meça a migração pelo fluxo de valor: lead time, frequência de deploy e MTTR são a régua; número de serviços extraídos é vaidade.
  • Monolito distribuído é a armadilha dominante: chamada síncrona em cadeia recria o acoplamento com pior latência, e a regra de profundidade precisa ser fiscalizada.
  • Mantenha o monolito vivo: congelar o legado transforma migração em guerra com o produto, e a entrega de features no monolito continuou até o fim.
  • Inventário de uso antes de extrair: 15% das features candidatas morreram por falta de uso, e o módulo novo nasceu menor.
  • Observabilidade antecede extração: os traces de produção apontaram os acoplamentos reais que o diagrama da arquitetura não mostrava.
  • A rota reversível vale o esforço: rollback de extração em minutos pelo roteador deu coragem para acelerar as fases seguintes.