Do Caos à Modernidade: O que aprendi migrando um YMS do Angular 16 ao 21 sozinho (e o mito do prazo de 2 semanas)
No mundo corporativo, é muito comum ouvirmos frases como: "É só rodar um comando de update" ou "Dá pra fazer isso rapidinho". Recentemente, recebi o desafio de migrar o frontend de um sistema de gestão de pátio logístico (YMS - Yard Management System) do Angular 16 para a versão 21.
O prazo inicial estimado pela gestão? 2 semanas.
Hoje, dois meses depois, o projeto está rodando liso na versão nova, mas ainda estamos fazendo ajustes e "pentes finos". Quero compartilhar o que aprendi nessa jornada atuando como "exército de um homem só", os ganhos técnicos absolutos e a realidade de conciliar inovação com a manutenção de sistemas legados.
O Cenário: A Complexidade de um YMS e o Desafio do Legado
Para dar contexto, um YMS não é um CRUD simples. Estamos falando de um sistema "vivo" que gerencia entrada e saída de caminhões, pesagem em balanças, fluxos logísticos complexos e monitoramento em tempo real. Qualquer erro no frontend pode literalmente parar uma frota de caminhões.
Fazer essa migração sozinho já seria um desafio monumental, mas a realidade bateu à porta: nossos clientes on-premise.
Como temos clientes que utilizam o sistema instalado localmente (e que não atualizam a infraestrutura com frequência), eles precisavam continuar na versão 16. Conciliar o suporte contínuo para essa base legada enquanto eu reescrevia e modernizava a arquitetura para o Angular 21 exigiu um controle de versão impecável e muita sanidade mental.
Faxina Arquitetural: O Fim dos NgModules
O ponto de virada dessa migração não foi apenas a troca de versão, mas a decisão de abandonar completamente os NgModules. Ao migrar para Standalone Components, o projeto passou por uma auditoria forçada.
Sem a "caixa preta" dos módulos, que muitas vezes escondiam dependências e componentes que ninguém mais usava, ficou evidente o quanto de código morto o sistema carregava. Aproveitei esse movimento para remover bibliotecas órfãs e telas legadas. O resultado foi uma redução da pasta src de 40MB para 20MB. Essa limpeza reduziu drasticamente a carga cognitiva: builds locais ficaram mais velozes e a indexação da IDE tornou-se instantânea.
Para sustentar o crescimento do sistema sem transformar o código em um emaranhado de dependências, decidi abandonar a clássica organização por "tipo de arquivo" (onde todos os serviços e componentes ficam amontoados em pastas globais). Em um domínio denso como um YMS, a arquitetura Feature-based é o que garante a sanidade: cada funcionalidade passa a ser um ecossistema independente e encapsulado.
Isso nos trouxe alta coesão funcional: se preciso dar manutenção na lógica de pesagem, tudo o que preciso, do componente à regra de negócio, está dentro da mesma pasta. Isso elimina aquele vaivém cansativo entre diretórios espalhados por todo o projeto e torna o isolamento de contexto muito mais claro:
src/
└── app/
├── core/ # Singleton, Auth, Guards, Interceptors
├── features/ # O coração do negócio isolado
│ ├── autenticacao/
│ ├── frota/ # Tudo sobre pesagem fica APENAS aqui
│ ├── logistica/
│ └── relatorios/
└── shared/ # UI Components, Pipes, Diretivas globais
Refatoração: Menos Boilerplate, Mais Performance
Aproveitei o motor do Angular moderno para refatorar visualmente as telas, deixando a UI mais fluida, mas foi no TypeScript que a mágica aconteceu. Separei exemplos que mudaram meu dia a dia:
1. O Fim do "Inferno do super()" com inject()
Um dos maiores ganhos de produtividade foi em nossos componentes e serviços genéricos. No padrão antigo, qualquer componente filho que herdasse de uma classe pai precisava passar todas as dependências via super() no construtor. Se o pai tivesse 5 dependências, o filho era obrigado a declará-las apenas para repassá-las.
Com o inject(), o componente filho "automaticamente" herda as dependências do pai sem nenhum boilerplate extra no construtor. O código ficou absurdamente mais limpo.
Antes (Angular 16 - Verboso):
@Component({ ... })
export class FilhoComponent extends PaiComponent {
constructor(
private service: LogisticaService,
private router: Router
) {
super(service, router); // Repetição desnecessária
}
}
Depois (Angular 21 - Moderno):
@Component({ standalone: true, ... })
export class FilhoComponent extends PaiComponent {
// O filho já possui acesso às dependências injetadas no Pai via inject()
// sem precisar de construtor ou super()
}
2. Resolvendo Memory Leaks com takeUntilDestroyed()
No monitoramento do pátio, lidamos com muitos fluxos reativos. Antes, eu precisava criar um Subject de destroy, implementar o OnDestroy e disparar o evento em toda tela para a memória do navegador não estourar. Agora, o framework cuida disso de forma nativa e segura.
Antes (Gerenciamento Manual):
destroy$ = new Subject<void>();
ngOnInit() {
this.service.getDadosCaminhao()
.pipe(takeUntil(this.destroy$))
.subscribe();
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
Depois (Automático):
ngOnInit() {
this.service.getDadosCaminhao()
.pipe(takeUntilDestroyed())
.subscribe();
}
O "Pente Fino" e a Esteira de Qualidade
Migrar de versão não adianta nada se a cultura de desenvolvimento não mudar. Mergulhei no código para fazer uma faxina implacável: removi centenas de comentários inúteis gerados por IAs que só atrapalhavam a leitura e deletei os famosos console.log de debug esquecidos.
Para blindar o projeto daqui para frente, modernizei todo o ferramental:
- Testes na velocidade da luz: Substituí o antigo Karma/Jasmine pelo Vitest, acelerando absurdamente o feedback loop, e adicionei Cypress para garantir os fluxos críticos de entrada/saída de veículos.
- Padronização forçada (no bom sentido): Configurei o ESLint e o Husky com regras rígidas. O código só passa se estiver no padrão.
- Commits Semânticos: Adicionei o
.commitlintrc. Agora seguimos um fluxo de Conventional Commits e code review obrigatório antes de qualquer integração.
A Realidade sobre a IA e o Prazo
Todo esse esforço gerou um impacto colossal na ponta: o tamanho do pacote final enviado ao cliente (bundle) caiu de 40MB para apenas 20MB. O sistema está absurdamente mais rápido através de Tree-Shaking eficiente.
Mas voltemos ao prazo de 2 semanas. Ele era inviável. A Inteligência Artificial ajuda muito? Demais. Ela acelerou a refatoração sintática repetitiva e me salvou de horas de digitação. Porém, a IA não tem o contexto do negócio. Ela não sabe que se o módulo da balança falhar, a empresa toma multa. As decisões arquiteturais, o isolamento das features e o "pente fino" de qualidade são um trabalho puramente humano.
Conclusão
Essa jornada de 2 meses (que ainda continua com as melhorias contínuas) me transformou como profissional. Saí do papel de "alguém que escreve código Angular" para assumir uma visão real de Engenharia de Software. O projeto hoje é outro: robusto, testável, escalável e pronto para o futuro.
Atualizar versão não é luxo, é pagar dívida técnica.
E vocês da comunidade, já tiveram que encarar uma migração crítica e complexa sozinhos com prazos irreais? Como lidaram com o embate entre a expectativa da gestão e a necessidade técnica de fazer a coisa certa?