Dependency Management – When Should Dependencies Be Updated?

dependenciesdependency-management

We had two major dependency-related crises with two different code bases (Android, and a Node.js web app). The Android repo needed to migrate from Flurry to Firebase, which required updating the Google Play Services library four major versions. A similar thing happened with our Heroku-hosted Node app where our production stack (cedar) was deprecated and needed to be upgraded to cedar-14. Our PostgreSQL database also needed to update from 9.2 to 9.6.

Each of these apps' dependencies sat stale for almost two years, and when some were deprecated and we reached the 'sunset' period, it has been a major headache to update them, or replace them. I've spent over 30 hours over the past month or two slowly resolving all of the conflicts and broken code.

Obviously letting things sit for two years is far too long. Technology moves quickly, especially when you're using a platform provider like Heroku. Let's assume that we have a full-fledged test suite, and a CI process like Travis CI, which takes a lot of the guesswork out of updating. E.g. if a function was removed after an upgrade, and you were using it, your tests would fail.

How often should dependencies be updated, or when should dependencies be updated? We updated because we were forced to, but it seems that some kind of pre-emptive approach would be better. Should we update when minor versions are released? Major versions? Every month if updates are available? I want to avoid a situation like what I just experienced at all costs.

PS – for one of my personal Rails projects, I use a service called Gemnasium which tracks your dependencies so that you can be notified of e.g. security vulnerabilities. It's a great service, but we would have to manually check dependencies for the projects I mentioned.

Best Answer

You should generally upgrade dependencies when:

  1. It's required
  2. There's an advantage to do so
  3. Not doing so is disadvantageous

(These are not mutually exclusive.)

Motivation 1 ("when you have to") is the most urgent driver. Some component or platform on which you depend (e.g. Heroku) demands it, and you have to fall in line. Required upgrades often cascade out of other choices; you decide to upgrade to PostgreSQL version such-and-so. Now you have to update your drivers, your ORM version, etc.

Upgrading because you or your team perceives an advantage in doing so is softer and more optional. More of a judgment call: "Is the new feature, ability, performance, ... worth the effort and dislocation bringing it in will cause?" In Olden Times, there was a strong bias against optional upgrades. They were manual and hard, there weren't good ways to try them out in a sandbox or virtual environment, or to roll the update back if it didn't work out, and there weren't fast automated tests to confirm that updates hadn't "upset the apple cart." Nowadays the bias is toward much faster, more aggressive update cycles. Agile methods love trying things; automated installers, dependency managers, and repos make the install process fast and often almost invisible; virtual environments and ubiquitous version control make branches, forks, and rollbacks easy; and automated testing let us try an update then easily and substantial evaluate "Did it work? Did it screw anything up?" The bias has shifted wholesale, from "if it ain't broke, don't fix it" to the "update early, update often" mode of continuous integration and even continuous delivery.

Motivation 3 is the softest. User stories don't concern themselves with "the plumbing" and never mention "and keep the infrastructure no more than N releases behind the current one." The disadvantages of version drift (roughly, the technical debt associated with falling behind the curve) encroach silently, then often announce themselves via breakage. "Sorry, that API is no longer supported!" Even within Agile teams it can be hard to motivate incrementalism and "staying on top of" the freshness of components when it's not seen as pivotal to completing a given sprint or release. If no one advocates for updates, they can go untended. That wheel may not squeak until it's ready to break, or even until has broken.

From a practical perspective, your team needs to pay more attention to the version drift problem. 2 years is too long. There is no magic. It's just a matter of "pay me now or pay me later." Either address the version drift problem incrementally, or suffer and then get over bigger jolts every few years. I prefer incrementalism, because some of the platform jolts are enormous. A key API or platform you depend on no longer working can really ruin your day, week, or month. I like to evaluate component freshness at least 1-2 times per year. You can schedule reviews explicitly, or let them be organically triggered by the relatively metronomic, usually annual update cycles of major components like Python, PostgreSQL, and node.js. If component updates don't trigger your team very strongly, freshness checks on major releases, at natural project plateaus, or every k releases can also work. Whatever puts attention to correcting version drift on a more regular cadence.

Related Topic