Dependencies suck. In this blog post I describe how I choose dependencies for my projects and business.
It is no secret; developers love to use new tools and technologies. However, my experience has taught me it is often in your best interest to not chase new technology and instead focus on a few core technologies and tools.
The following usually occurs shortly after using a new technology for a business or project:
- The honeymoon phase ends and the technology doesn't feel so exciting any more.
- The promises of the technology fade as you and/or the rest of the team come to terms with the technology's strengths and weaknesses.
- You end up with a codebase that is not very reusable or portable to other projects or products. This may be due to technical factors or the simple fact that the team is off chasing the next hot tech leaving your current codebase, architecture and learnings in the dust.
- Your codebase ends up being fragile and slowly decays from dependency rot.
- Newer technology comes along and nullifies much of what you did, requires updating, or worse, a rewrite (see the point above).
I follow a few guiding principles when choosing my dependencies and tools. The specific tech and tools I use are not really important. However, the principles behind why they were chosen are important.
The primary goal driving my dependency decisions is simple; I want to build software solutions that are reusable, reliable, secure, and require minimal maintenance. If your goals are vastly different than mine you can probably stop reading now.
A long running inside joke of mine is something I have dubbed, "bundle anxiety". If you have worked with a Ruby on Rails (or a Node based codebase) you can probably relate. Have you ever run bundle update after a few weeks (or months) of not updating? If so, did you see a flood of updates coming in and your stomach begins to sink? The anxiety that builds up before running update; that's bundle anxiety.
Don't get me wrong— I love Ruby on Rails. I used the framework for years (personally and professionally). However, after maintaining a number of services in production I found the ecosystem to be a bit too fragile for my liking. The anxiety of running bundle update is real. I still have services running in production I cannot fully update due to incompatibles between various Rails engines and gems that are in use.
At the end of the day, dependencies need to be dependable. So how do we choose dependencies we can safely depend on? The guiding principles I use are as follows:
- Pick dependencies that are fundamental and rely on less abstraction.
- Strive for minimal dependencies.
- Own your dependencies.
Rather than relying on one or more frameworks, consider relying on the fundamental technologies instead. This is often considered boring and not fun, but maybe that is a good thing.
The closer you stick to the fundamentals the more you can rely on it (fundamentals tend to not change very often). This usually means skipping new and exciting technology. Furthermore, the more skilled and experienced you become with the fundamentals the more transferable your skills become between projects. In addition to transferring skills between projects you can often share solutions (code) and learnings between projects.
For a web application, a comparison between fundamental and fun tech would look something like the following:
|HTML||Slim, Haml, Jade, JSX|
|Server Side Rendering||Single Page Applications|
Stay with the fundamentals unless you have a very compelling reason not to.
In a perfect world you would have no dependencies. You could write your own software from the ground up and ship it. There are companies and industries where this mindset is the norm. This mindset is often associated with the "Not Invented Here" syndrome. However, creating everything from the ground up is not very practical for most businesses or projects.
A more practical approach is to minimize dependencies. There is more to minimizing dependencies than choosing fewer of them. It is also about carefully choosing dependencies that themselves don't have many dependencies. An ideal dependency would have no dependencies. These are often pitched as
pure-<insert language>, or
header only libraries.
Frameworks are rarely minimal and they don't solve all of your problems. As a result most projects end up using one or more frameworks combined with a number of libraries, and that is where the bundle anxiety begins to ramp up.
Pick fewer dependencies, and don't evaluate a dependency based on the dependency alone. Look closer and find out which dependencies it relies on and then decide if you are willing to take on all of the dependencies.
These days it is all too common for developers to add a dependency without a second thought. Fewer and fewer developers actually look at the code of their dependencies. Even fewer take the time to read and understand their dependencies. Don't be one of those developers.
I am of the mindset that you own your dependencies. If your business or product relies on the dependency then it is your responsibility to take care of the dependency. At minimum, this means:
- You should know what the dependency relies on.
- You should understand how the dependency works.
- You should be ready and willing to fix the dependency when (not if) it breaks.
Package managers make adding dependencies a breeze, but they also put a lot of distance between you (the developer) and the dependency. In order to own (and maintain) dependencies I prefer to do the following:
- Vendor your dependencies.
- Add a service layer between your code and your dependencies (when feasible).
Go forth and build awesome stuff. Hopefully while breaking less along the way.