Desde o inicio da era dos desenvolvedores, buscamos cada vez mais a consistência no processo de desenvolvimento. A consistência facilita a automatização de etapas como entrega contínua, integração contínua e deploy contínuo.
Uma das coisas mais importantes para manter a consistência é saber gerenciar as dependências de uma aplicação, ou seja, garantir que as mesmas dependências usadas em desenvolvimento irão também ser usadas por todo o tipo de processo que pegar esse código, inclusive produção.
O Twelve-Factor App fala sobre dependências consistentes em um dos tópicos disponíveis em https://12factor.net/pt_br/dependencies.
NPM e dependências
Como sabemos, o NPM (Node Package Manager) é o gerenciador de dependências mais usado no ambiente Node.JS e também está tomando cada vez mais espaço nos frameworks front-end. Nele, é possível descrever quais dependências serão utilizadas, tanto para produção quanto para desenvolvimento. Ele usa o padrão de versionamento chamado semantic versioning, no qual é possível definir algumas regras para as dependências, como aceitar versões apenas do tipo path/bugfix ou minor.
O problema
O package.json a seguir possui uma lista de dependências, todas elas usam o versionamento semântico.
"dependencies": { "babel": "^6.5.0", "babel-cli": "^6.11.4", "babel-preset-es2015": "^6.9.0", "body-parser": "^1.15.2", "express": "^4.14.0", "http-status": "^0.2.3", "sequelize": "^3.23.6", "sqlite3": "^3.1.4" }
Notem que antes de cada versão temos um ^ – esse símbolo significa que vamos aceitar qualquer minor release desses módulos. Por exemplo o “babel”: “^6.5.0” seria o mesmo que “babel”: “^6.*”.
Isso significa que toda a vez que eu rodar npm install, ele vai instalar a versão mais nova dessas dependências.
Imagine que terminamos nossa task de sexta feira e commitamos, os testes passaram, tudo lindo, quando atualizamos a produção BUMMMMM!!! alguma dependência atualizou e quebrou nossa aplicação. Ué, mas em desenvolvimento estava funcionando? Como assim? Aí vamos checar o repositório da dependência e vemos que saiu uma versão durante o intervalo que estávamos fazendo deploy em produção. Parece louco, né? Improvável? Na verdade, é bem comum.
Como solucionar isso? Devo fixar minhas versões no package.json e garantir que sempre que rodar o npm install vai instalar as mesmas versões? Algo como “babel”: “6.5.0” ?
- Não, essa ainda não é a solução, pois além de ser muito trabalho manual devemos lembrar que dependências possuem dependências, e elas também podem quebrar nossa aplicação.
A solução
Queremos que quando alguém ou algum processo pegar nossa aplicação e rodar o comando npm install, a aplicação busque exatamente as mesmas versões que foram instaladas em modo de desenvolvimento, as mesmas dependências e versões, inclusive das dependências das dependências.
Uma solução comum para isso é ter um arquivo de .lock, que guarda as versões exatas de cada uma das dependências instaladas para que quando for rodado o comando de install ele instale as versões exatas. O npm não possuía isso há algum, mas de tanto o pessoal reclamar e pressionar foi desenvolvido o npm shrikwrap.
Fixando versões com o NPM Shrinkwrap
O npm shrinkwrap é um comando nativo do npm que cria um arquivo chamado npm-shrinkwrap.json com as versões atuais instaladas de cada pacote, como no exemplo abaixo:
O npm shrinkwrap foi feito para ser usado em modo de produção. Para usar em desenvolvimento, faça como no exemplo abaixo:
npm shrinkwrap --dev //adiciona também as devDependencies
Caso eu delete minha pasta node_modules ou faça a instalação do projeto do zero e rode npm install, o npm vai ler o npm-shrinkwrap.json e instalar as versões salvas nele.
Atualizando dependências
Para atualizar dependências, rodamos o comando npm update, que vai verificar as versões que podem ser atualizadas e irá instalar suas atualizações. Veja a imagem abaixo:
O exemplo acima mostra que o babel foi atualizado para a versão 6.5.1; agora o npm-shrinkwrap.json está desatualizado; para que ele volte a refletir as versões corretas, vamos novamente rodar o comando npm shrinkwrap para gerar um novo npm-shrinkwrap.json atualizado.
Deploy de aplicações e versionamento
Devemos sempre commitar o package.json e também o npm-shrinkwrap.json para o controle de versão, seja ele git, mercurial ou qualquer outro, pois assim todo mundo que pegar esse código terá a lista de versões utilizadas na ultima atualização do código.
Até mais, pessoal!