Refatorar monólito sem detonar produção
Microsserviço não é a solução. Refatorar monólito é uma arte — strangler fig, feature flag, migração de schema em passos. O que ninguém te conta sobre o processo.
Refatorar monólito sem detonar produção
Existe uma mentira que LinkedIn adora repetir:
"Migre seu monólito pra microsserviços."
Não.
90% dos casos: você deveria refatorar o monólito.
Não dividir.
E refatorar monólito grande é uma arte específica — que ninguém ensina porque "não é sexy".
Primeiro: por que refatorar?
Refatorar não é "deixar bonito".
Refatorar é:
- reduzir tempo de onboarding de devs novos
- reduzir bug rate
- aumentar velocidade de entrega
- aumentar confiança em mudanças
Se não tem dor de ENTREGA, talvez não precise refatorar agora.
Refatoração sem dor real é vaidade técnica.
A regra número 1: testes antes de tudo
Você não refatora código sem teste.
Você reescreve. E reescreve quebra.
Se a área que você quer mexer não tem cobertura:
- Para. Escreve teste pra comportamento atual.
- Garante que passa.
- Aí refatora.
- Roda teste a cada mudança pequena.
Isso é o "boy scout rule" reverso: deixe a base testada antes de partir pra ação.
Strangler Fig — a estratégia que funciona
Termo cunhado pelo Martin Fowler.
Ideia: você não substitui o sistema todo de uma vez. Você cresce algo novo ao redor do antigo, e vai migrando rota por rota até o velho ser apenas uma casca.
[Cliente]
↓
[Router]
├── rota A → código novo
├── rota B → código novo
├── rota C → código antigo
└── rota D → código antigo
Com o tempo, todas as rotas migram. Você apaga o velho.
Estratégia oposta: big bang rewrite. Falha em 90% dos casos.
Inventário antes da ação
Antes de tocar em código, mapeie:
- quais módulos existem
- quais dependem de quais
- quais têm cobertura de teste
- quais mudam com mais frequência (git log frequency)
- quais geram mais bugs (issue tracker)
Cruze essas três últimas: alta mudança + alto bug + baixa cobertura = comece por aqui.
Esse é o ROI maior.
Padrões pra extrair contexto
1. Domain object
Substitua "service class fofa" por objeto de domínio com identidade própria.
# antes
UserService.process(user, params)
# depois
PaymentRequest.new(user: user, amount: amount).submit
Não é só renomeação. É deslocar comportamento pra onde ele pertence.
2. Value object
Tudo que é "primitivo cercado de regra" merece ser objeto:
# antes — Money espalhado como integer
total_in_cents = price * quantity
formatted = "R$ #{total_in_cents / 100.0}"
# depois — Money class
total = Money.new(price) * quantity
formatted = total.format(:brl)
Encapsula regra. Reduz bug. Permite reuso.
3. Bounded context
Inspirado em DDD. Não precisa virar microsserviço — pode ser apenas namespacing rigoroso:
app/
├── billing/
│ ├── invoice.rb
│ ├── payment.rb
│ └── subscription.rb
├── catalog/
│ ├── product.rb
│ └── category.rb
└── identity/
├── user.rb
└── session.rb
Cada pasta = um contexto. Comunicação entre eles via interface explícita, não via cross-reference direto.
Isso é tudo que "modular monolith" significa.
Feature flag é o segundo melhor amigo
Quer migrar uma rota crítica? Não troque tudo de uma vez.
if Flag.enabled?(:new_checkout, user)
NewCheckout.process(order)
else
OldCheckout.process(order)
end
Rola para 1% dos users. Mede. Aumenta. Mede. Aumenta.
Quando 100% rodou tranquilo por X semanas, apaga o antigo.
Sem feature flag: ou você lança e reza, ou cria branch de longa vida que vira pesadelo de merge.
Migrações de banco precisam de cuidado triplo
Refatorar código é uma coisa. Migrar schema é outra.
Regras:
1. Mudança quebrante = vários deploys.
Passo 1: adiciona coluna nova (compatível com código atual)
Passo 2: aplicação começa a escrever na coluna velha E na nova
Passo 3: backfill da coluna nova
Passo 4: aplicação lê da coluna nova
Passo 5: para de escrever na velha
Passo 6: dropa a velha
Cada passo é deploy separado.
2. Nunca adicione coluna NOT NULL sem default em tabela grande.
Lock total. Aplicação trava.
3. Cuidado com ALTER TABLE em tabela com milhões de linhas.
Use pg_repack, strong_migrations gem, ou estratégias específicas do seu banco.
O cemitério de código
Antes de refatorar, descubra o que pode morrer.
Ferramentas:
rubocopcom regra deLint/UselessAssignmentcoveragerodando em produção (gemcoverband)- log de uso de endpoint (se ninguém chama há 6 meses, considere deletar)
Refatorar código morto é desperdício.
Apague antes.
O que não fazer
- Reescrever do zero sem entender o sistema atual.
- Refatorar várias áreas em paralelo (cada uma vira branch infernal).
- Refatorar e adicionar feature no mesmo PR.
- Fazer "limpeza" sem teste de regressão.
- Sair do escopo do PR pra "consertar mais coisas".
Refatoração é cirurgia. Não é faxina.
Métricas pra acompanhar
Antes da refatoração:
- lead time (PR aberto → produção)
- bug rate por área
- tempo de onboarding
- coverage por contexto
Depois:
- mesmas métricas
- comparar
- mostrar pra liderança que valeu a pena
Sem métrica, refatoração vira "ele fica mexendo em código velho ao invés de entregar feature".
E aí você perde o orçamento político pra continuar.
A grande virada de chave
Refatorar monólito não é destruir e reconstruir.
É:
- escolher onde tem ROI real
- proteger com teste
- mudar em pequenos pedaços
- usar feature flag pra de-risk
- medir antes e depois
Quem segue isso, refatora monólitos enormes com confiança.
Quem ignora, reescreve achando que "vai ser rápido" — e leva 2 anos no que deveria ser 3 meses.
Conclusão
Monólito não é o vilão.
Monólito mal cuidado é.
Microsserviço não é a solução. É um trade-off com custos enormes — operacional, de complexidade, de latência, de debug.
A maturidade técnica é entender que:
- monólito modular bem feito vence microsserviços mal feitos
- refatoração consistente vence reescrita revolucionária
- contexto bem desenhado vence service crap espalhada
Refatorar bem é menos sobre código, e mais sobre disciplina de processo.
Quem aprende isso muda a carreira inteira.
