pinning your hopes

originally published: Feb 2012

This is a post about the npm (node package management) and how I think it got versioning wrong in the package.json file. If you don't care about node there are still relevant items in this post about dependencies in development and deployment.

At first I was going to write a blog post about how the fuzzy versions are the wrong way to do it, but found this post that covered the topic. However I think this post, and others that talk about the fuzzy version issue are missing the core fundamentals behind WHY fuzzy versions are bad for development and deployment.

My argument stems from a few statements which I treat as axioms. I will try to address how each one relates to fuzzy versioning and why it matters.

Often when I hear about fuzzy versions in the node community it goes hand in hand with talk of semver. The idea of semver is good and can be used regardless of how you handle dependencies, but what semver does not expose is that even a change to version 0.0.x of a package can introduce a bug. For a dynamic language it could be as simple as a typo or a missing 'var' keyword. The point is not about the bug but that it has been introduced even in a version that is considered a "safe" point release. Now, if your library has not pinned its dependencies, this bug now becomes your bug. More critically (and as the referenced npm post states) running 'npm install' at two different times will yield two different codebases. The bottom line here is that semver is not a static analysis or a test tool but a way to communicate to another *person *about what types of changes they should expect and check for. It is not a substitute for a developer actually checking the changelog and doing testing. Maybe you don't care about this "risk" in production, but when you are writing a library, these risks are not for you to access.

When npm was first introduced it was touted as a tool for "development" versus "deployment". An argument developers make is that the fuzzy versions help them iterate quicker. I have yet to see "proof" of this but that is not the point. Fuzzy versions hurt development groups as well and for the same reasons as listed above. If I pull down your clean codebase and the versions are fuzzy, I may be getting versions which the original developer tested with. Now when I begin developing I am working with new "quirks" and more importantly wasting time debugging problems that no one else will be able to reproduce on their codebase until they wipe their dependencies and reinstall. I have not only wasted my time but other developer time simply because of a "harmless" point release. If you want to iterate quickly you can still do it, pinned versions in the package.json file do not stop you. Npm already has 'npm link' for local development. One of the less used features of npm versions is the ability to specify a tarball url or git repo to install from. THIS is how your team can iterate quickly on branches if it so chooses, but these should never be published to npm as such.

The final bullet point has to do with the generic idea of deployment failure. Another argument I have heard against using npm for deployment is that it is flaky, my firm has better (read different) ways, or that binaries are annoying. I am not trying to say your ways are worse or that the npm website can't go offline (which can be protected against), but what I am going to argue is that a failure during deployment is better than during runtime. In the case of binaries and compiled code, you will know right away if your deployment worked or did not. If it failed, you can take action immediately without the error being deferred to runtime. There is no "partial" deployment with npm, it worked or it didn't. If you use fuzzy versioning, the deployment can work just fine yet defer the error to runtime.

Npm is addressing some of these issues with the new npm shrinkwrap command. This creates yet another .json file in your repo. To me, this is just a "patch" on top of the real problem. Yes, it does "solve" the issue of pinned packages, but it does so in a poor way. Instead of keeping the packaging process simple with just the package.json file, another command and file are introduced. This seems like an admission that the versions should have been pinned from the start but instead of fixing that (which is probly impossible at this point) a new file is created. Nasty but arguably pragmatic.

This is not a post to hate on npm, in fact I think npm has many great features. I use npm daily for deployments and they always go smoothly (yes I use pinned versions). One last note about what makes package pinning great with npm: dependencies are localized! Each package maintainer can depend on their own desired package versions and test with those versions. The prominent people in the node community often develop many packages and don't want to update every single one when things change, however, they are in the nice position of being the maintainers and know what they are doing. When you are the consumer of a package, you don't always have such luxuries or trust and opening a communication channel is not the way to solve the problem. You cannot just tell someone they must vet changes by you or stick to semver (which I have shown isn't foolproof).

- home -