Gerenciando objetos antigos e atuais no dia a dia
Todo sistema que existe há mais de dois anos lida com isso. Um objeto foi definido de um jeito, a necessidade mudou, e agora você tem duas versões circulando pela codebase. O problema não é teórico. É operacional. A abordagem mais comum envolve três camadas: leitura, migração e escrita. Quando você recebe um dado legado, precisa detectar o formato, aplicar transformações estruturais e depois persistir usando o esquema atual. Fazer isso de forma transparente evita quebrar clientes que ainda enviam payloads antigos.
objeto antigo e atual: o que precisa mudar
Um campo que era opcional virou obrigatório. Um enum ganhou dois valores novos. Um objeto aninhado foi achatado. Essas são as mudanças mais frequentes que eu vejo acontecerem em projetos reais. O que ninguém conta é que a parte difícil não é a migração em si. É a validação cruzada. Você precisa garantir que o objeto resultante após a transformação passe nos mesmos checks que um objeto nativamente atual passaria. Se pular essa etapa, os erros aparecem só em produção, quando o serviço downstream rejeita o dado mal formatado.
Eu tive um caso específico onde um campo timestamp em um objeto antigo vinha como string no formato DD/MM/YYYY HH:mm, enquanto o esquema atual esperava ISO 8601. A migração parecia simples até eu perceber que havia clientes enviando datas com fuso horário errado porque o formato antigo não incluía offset. A solução foi normalizar tudo para UTC antes de converter para ISO, usando uma regex que extraía o prefixo de data e hora e depois aplicava o offset padrão do sistema. Isso resolveu os casos borda que estavam causando inconsistências silenciosas no banco de dados. Aqui vai algo contra intuitivo: muitas vezes você não deve migrar o objeto antigo para o novo completamente. Às vezes o certo é manter os dois formatos lado a lado durante um período de transição e usar um mecanismo de versionamento de schema. Um campo _version no payload que indica qual estrutura está sendo usada permite que você processe cada objeto pelo seu parser correspondente sem precisar de uma migração única e destrutiva.
Outro detalhe que iniciantes costumam errar é a ordem das transformações. Se você tem uma migração que adiciona um campo calculado baseado em outro campo que também está sendo migrado, a ordem importa. Aplicar primeiro a adição do campo e depois a transformação do campo fonte gera valores incorretos. A sequência correta é sempre: transformar campos fonte, depois calcular campos derivados, depois adicionar campos novos. O principal limitante dessa abordagem é que ela exige manutenção contínua. Cada versão antiga que permanece ativa no sistema aumenta o custo de suporte. Quanto mais tempo um objeto antigo fica sendo tratado separadamente, mais difícil fica garantir que ele ainda seja compatível com todas as regras de negócio atuais. Na prática, sistemas que mantêm mais de três versões ativas de um mesmo objeto acabam tendo taxas de erro significativamente maiores do que sistemas que fazem migrações progressivas e eliminam versões antigas dentro de ciclos de release.
👉 Clique no botão abaixo para saber mais sobre o assunto!
Se o seu cenário envolve um volume alto de objetos antigos e a migração manual seria inviável, considere usar um mapper declarativo em vez de lógica procedural. Defina regras de transformação como configuração, não como código. Isso facilita testes unitários e permite que novos membros da equipe entendam rapidamente como cada campo é convertido.
passo a passo prático
Comece listando todos os campos que existem no objeto antigo e marque quais têm correspondência direta no objeto atual. Campos sem correspondência direta são os problemáticos. Para cada um deles, decida se será descartado, convertido ou mapeado para um campo existente com transformação. Depois, defina os constraints do objeto atual. Quais campos são obrigatórios? Quais têm validação específica? Anote isso antes de escrever qualquer código de migração, porque a validação costuma ser o que mais gera retrabalho.
Implemente a função de migração como uma transformação pura: receba o objeto antigo, retorne o objeto atual. Sem efeitos colaterais. Sem acesso a banco de dados dentro da função. Isso permite testar com dados fake e garantir que a lógica está correta antes de integrá-la ao fluxo principal. Teste com dados reais de produção, não com dados sintéticos. Conjuntos de teste criados artificialmente nunca capturam as anomalias que aparecem em dados gerados por usuários ao longo de meses. Peça acesso a logs ou snapshots anonimizados e rode a migração neles. Meça o percentual de falhas e ajuste as regras até atingir uma taxa aceitável.
Por fim, documente as mudanças. Não apenas o que mudou no esquema, mas por que mudou. Versões futuras da sua equipe vão agradecer por saber o contexto da decisão, não apenas a descrição técnica do campo novo.