Photo by JJ Ying / Unsplash

Dependency Hell

Architecture Aug 27, 2021

Modern software development has evolved over the past several years with many languages and frameworks supporting plugins or libraries. These libraries and plugins have made developing complex software significantly easier and it's been a huge value to our workflow as software developers. That being said this evolution seems to have bread a new trend in software development that can be harmful if the developers are not careful and that is that many people look for a library to solve a problem before attempting to solve it themselves. This leads to people installing libraries to accomplish simple tasks, and while this makes development easier, it begins adding significant dependencies on the project. Each dependency starts a whole tree of dependencies; libraries that depend on libraries that depend on libraries.

Dependency Tree

When you install a library let's say for example a NodeJS library from npm you are actually installing many libraries because you're installing your 1 dependency plus that dependency's dependencies and so on. To make this clear, there's an example of the popular NodeJS framework express entire dependency tree at the bottom of this article (because it's so long). You must keep this in mind when you're writing software because it can quickly become an issue if you're installing hundreds if not thousands of libraries to be packaged with your project at runtime.

Dependency Weight

Each dependency in itself comes with added code, added assets and more which adds weight to your code base for example in React Native you can increase your compiled app size and decrease performance by adding many libraries to your app. If you're shipping a bundle of some sort to users you should always take file size and complexity into consideration. Even though we're living in an age where a large portion of users are using fairly powerful machines to access your software you still should be considerate where possible to avoid alienating a portion of your potential audience that doesn't have performant machines, or the portion of your audience that will look at your bundle and decide not to use it wether it's for performance reasons or sizing reasons.

Third Party Platforms

This concept can be applied to third party platforms as well for example if you are looking for a specific feature set to add to your platform and you find a third party platform that includes those features that you could integrate with, you can count that platform as a dependency regardless if it's user facing or if it's an internal program used by your team.

Making The Decision

Ultimately I think that the choice to utilize third party platforms comes down to a simple set of questions that you have to ask yourself or your team. It's important that you're honest with yourself and your team to make the right choice here because i've observed often that people will be convinced of a platform so much that they will fudge the answers to sway the answer.

  1. What features does this library or platform add?
  2. What features do I (we) need?
  3. How long would it take to build the features we need internally?
  4. How long would it take to build library or platform support for this dependency?
  5. Does this dependency include features that I (we) don't need?
  6. What are the downsides of using this dependency? (include size, etc.)
  7. How difficult is it to learn this dependency for the people that will be using it?
  8. Has this dependency been recently and frequently updated?
  9. How well is this dependency documented?

Here are some considerations to make when analyzing these answers to help you reach a decision:

  • If the dependency has not been updated in a while, skip it
  • If the dependency is not frequently updated, skip it
  • If the dependency is not well documented, skip it
  • If time taken to develop the features is less or close to the time to add and learn the library, skip it
  • If the dependency is huge and you only need a couple features, skip it
  • If the downsides outweigh the benefits, skip it
  • Always add time to integrate the dependency with time to learn the dependency for the people that will be using it together because if you develop a solution instead you can develop it with your users in mind where as often times you can't change the dependency. This means instead of saying "time to create these features is 5 hours and time to install and configure the dependency is 1 hour so we have to use it so it's 5 vs 1 hour" think "time to create these features is 5 hours, time to install and configure the dependency is 1 hour and time to learn is 6 hours so it's 5 vs 7 hours"

Personal Comments

As a Senior Software Developer I usually advocate against using third party platforms and services for the most part. Looking at things from an architectural point of view with vast amounts of experience gives you a different outlook on this. Not only do I advocate against it but I try my best to offer alternatives explaining what we could develop in-house and really clarifying the benefits of in-house development.

This of course is not for everything for example recently in my workplace we were discussing the use of a third party platform for pulling data from providers and formatting it for use with our platform. I was against it at first because to me it was an obvious band-aid fix for a problem we could solve quickly. This was until one of my coworkers explained the logging capabilities of this platform. At the time logging was huge for us; We really, really needed better logging and this had such an advanced system for logging it would take months for us to develop a reasonable equivalent. I quickly changed my mind, because the benefits of the platform outweighed the negatives and the answers to the questions I listed above changed.

When I develop simple public libraries and software for open source I do my best to avoid libraries all together where possible. Yes it adds more work for me but it's not difficult work and that's the whole point 😉


Further Discussion

How do you choose to use dependencies? Do you follow a similar approach? What do you think of this thought process and how would you expand upon it?


express  (v4.17.1 via npm)

➜  ~ npm-remote-ls express
└─ express@4.17.1
   ├─ content-type@1.0.4
   ├─ cookie-signature@1.0.6
   ├─ cookie@0.4.0
   ├─ array-flatten@1.1.1
   ├─ accepts@1.3.7
   │  ├─ negotiator@0.6.2
   │  └─ mime-types@2.1.32
   │     └─ mime-db@1.49.0
   ├─ debug@2.6.9
   │  └─ ms@2.0.0
   ├─ body-parser@1.19.0
   │  ├─ bytes@3.1.0
   │  ├─ content-type@1.0.4
   │  ├─ depd@1.1.2
   │  ├─ debug@2.6.9
   │  ├─ on-finished@2.3.0
   │  ├─ http-errors@1.7.2
   │  │  ├─ depd@1.1.2
   │  │  ├─ inherits@2.0.3
   │  │  ├─ statuses@1.5.0
   │  │  ├─ setprototypeof@1.1.1
   │  │  └─ toidentifier@1.0.0
   │  ├─ iconv-lite@0.4.24
   │  │  └─ safer-buffer@2.1.2
   │  ├─ raw-body@2.4.0
   │  │  ├─ bytes@3.1.0
   │  │  ├─ iconv-lite@0.4.24
   │  │  ├─ unpipe@1.0.0
   │  │  └─ http-errors@1.7.2
   │  ├─ type-is@1.6.18
   │  └─ qs@6.7.0
   ├─ escape-html@1.0.3
   ├─ depd@1.1.2
   ├─ etag@1.8.1
   ├─ content-disposition@0.5.3
   │  └─ safe-buffer@5.1.2
   ├─ finalhandler@1.1.2
   │  ├─ escape-html@1.0.3
   │  ├─ encodeurl@1.0.2
   │  ├─ debug@2.6.9
   │  ├─ parseurl@1.3.3
   │  ├─ statuses@1.5.0
   │  ├─ unpipe@1.0.0
   │  └─ on-finished@2.3.0
   ├─ fresh@0.5.2
   ├─ merge-descriptors@1.0.1
   ├─ methods@1.1.2
   ├─ on-finished@2.3.0
   │  └─ ee-first@1.1.1
   ├─ encodeurl@1.0.2
   ├─ parseurl@1.3.3
   ├─ path-to-regexp@0.1.7
   ├─ range-parser@1.2.1
   ├─ proxy-addr@2.0.7
   │  ├─ forwarded@0.2.0
   │  └─ ipaddr.js@1.9.1
   ├─ safe-buffer@5.1.2
   ├─ qs@6.7.0
   ├─ send@0.17.1
   │  ├─ destroy@1.0.4
   │  ├─ escape-html@1.0.3
   │  ├─ encodeurl@1.0.2
   │  ├─ depd@1.1.2
   │  ├─ etag@1.8.1
   │  ├─ fresh@0.5.2
   │  ├─ range-parser@1.2.1
   │  ├─ on-finished@2.3.0
   │  ├─ ms@2.1.1
   │  ├─ statuses@1.5.0
   │  ├─ http-errors@1.7.3
   │  │  ├─ depd@1.1.2
   │  │  ├─ inherits@2.0.4
   │  │  ├─ setprototypeof@1.1.1
   │  │  ├─ statuses@1.5.0
   │  │  └─ toidentifier@1.0.0
   │  ├─ mime@1.6.0
   │  └─ debug@2.6.9
   ├─ serve-static@1.14.1
   │  ├─ encodeurl@1.0.2
   │  ├─ escape-html@1.0.3
   │  ├─ parseurl@1.3.3
   │  └─ send@0.17.1
   ├─ utils-merge@1.0.1
   ├─ statuses@1.5.0
   ├─ vary@1.1.2
   ├─ ejs@2.6.1
   ├─ setprototypeof@1.1.1
   ├─ type-is@1.6.18
   │  ├─ media-typer@0.3.0
   │  └─ mime-types@2.1.32
   ├─ connect-redis@3.4.1
   │  ├─ debug@4.3.2
   │  │  └─ ms@2.1.2
   │  └─ redis@2.8.0
   │     ├─ double-ended-queue@2.1.0-0
   │     ├─ redis-commands@1.7.0
   │     └─ redis-parser@2.6.0
   ├─ after@0.8.2
   ├─ cookie-parser@1.4.5
   │  ├─ cookie-signature@1.0.6
   │  └─ cookie@0.4.0
   ├─ express-session@1.16.1
   │  ├─ cookie@0.3.1
   │  ├─ cookie-signature@1.0.6
   │  ├─ depd@2.0.0
   │  ├─ on-headers@1.0.2
   │  ├─ parseurl@1.3.3
   │  ├─ safe-buffer@5.1.2
   │  ├─ debug@2.6.9
   │  └─ uid-safe@2.1.5
   │     └─ random-bytes@1.0.0
   ├─ cookie-session@1.3.3
   │  ├─ cookies@0.7.3
   │  │  ├─ depd@1.1.2
   │  │  └─ keygrip@1.0.3
   │  ├─ on-headers@1.0.2
   │  └─ debug@2.6.9
   ├─ eslint@2.13.1
   │  ├─ concat-stream@1.6.2
   │  │  ├─ buffer-from@1.1.2
   │  │  ├─ inherits@2.0.4
   │  │  ├─ typedarray@0.0.6
   │  │  └─ readable-stream@2.3.7
   │  │     ├─ isarray@1.0.0
   │  │     ├─ inherits@2.0.4
   │  │     ├─ core-util-is@1.0.2
   │  │     ├─ process-nextick-args@2.0.1
   │  │     ├─ safe-buffer@5.1.2
   │  │     ├─ string_decoder@1.1.1
   │  │     │  └─ safe-buffer@5.1.2
   │  │     └─ util-deprecate@1.0.2
   │  ├─ doctrine@1.5.0
   │  │  ├─ esutils@2.0.3
   │  │  └─ isarray@1.0.0
   │  ├─ chalk@1.1.3
   │  │  ├─ escape-string-regexp@1.0.5
   │  │  ├─ ansi-styles@2.2.1
   │  │  ├─ has-ansi@2.0.0
   │  │  │  └─ ansi-regex@2.1.1
   │  │  ├─ supports-color@2.0.0
   │  │  └─ strip-ansi@3.0.1
   │  │     └─ ansi-regex@2.1.1
   │  ├─ debug@2.6.9
   │  ├─ escope@3.6.0
   │  │  ├─ es6-weak-map@2.0.3
   │  │  │  ├─ d@1.0.1
   │  │  │  ├─ es6-iterator@2.0.3
   │  │  │  ├─ es6-symbol@3.1.3
   │  │  │  └─ es5-ext@0.10.53
   │  │  ├─ esrecurse@4.3.0
   │  │  │  └─ estraverse@5.2.0
   │  │  ├─ estraverse@4.3.0
   │  │  └─ es6-map@0.1.5
   │  ├─ esutils@2.0.3
   │  ├─ file-entry-cache@1.3.1
   │  │  ├─ object-assign@4.1.1
   │  │  └─ flat-cache@1.3.4
   │  │     ├─ circular-json@0.3.3
   │  │     ├─ graceful-fs@4.2.8
   │  │     ├─ write@0.2.1
   │  │     │  └─ mkdirp@0.5.5
   │  │     └─ rimraf@2.6.3
   │  │        └─ glob@7.1.7
   │  ├─ estraverse@4.3.0
   │  ├─ espree@3.5.4
   │  │  ├─ acorn@5.7.4
   │  │  └─ acorn-jsx@3.0.1
   │  │     └─ acorn@3.3.0
   │  ├─ imurmurhash@0.1.4
   │  ├─ glob@7.1.7
   │  │  ├─ fs.realpath@1.0.0
   │  │  ├─ inherits@2.0.4
   │  │  ├─ inflight@1.0.6
   │  │  │  ├─ once@1.4.0
   │  │  │  └─ wrappy@1.0.2
   │  │  ├─ once@1.4.0
   │  │  ├─ minimatch@3.0.4
   │  │  └─ path-is-absolute@1.0.1
   │  ├─ es6-map@0.1.5
   │  │  ├─ d@1.0.1
   │  │  │  ├─ es5-ext@0.10.53
   │  │  │  └─ type@1.2.0
   │  │  ├─ es6-iterator@2.0.3
   │  │  │  ├─ es6-symbol@3.1.3
   │  │  │  ├─ d@1.0.1
   │  │  │  └─ es5-ext@0.10.53
   │  │  ├─ es6-symbol@3.1.3
   │  │  │  ├─ d@1.0.1
   │  │  │  └─ ext@1.4.0
   │  │  │     └─ type@2.5.0
   │  │  ├─ event-emitter@0.3.5
   │  │  │  ├─ d@1.0.1
   │  │  │  └─ es5-ext@0.10.53
   │  │  ├─ es5-ext@0.10.53
   │  │  │  ├─ es6-iterator@2.0.3
   │  │  │  ├─ next-tick@1.0.0
   │  │  │  └─ es6-symbol@3.1.3
   │  │  └─ es6-set@0.1.5
   │  │     ├─ d@1.0.1
   │  │     ├─ es6-symbol@3.1.1
   │  │     │  ├─ d@1.0.1
   │  │     │  └─ es5-ext@0.10.53
   │  │     ├─ es6-iterator@2.0.3
   │  │     ├─ es5-ext@0.10.53
   │  │     └─ event-emitter@0.3.5
   │  ├─ globals@9.18.0
   │  ├─ ignore@3.3.10
   │  ├─ is-resolvable@1.1.0
   │  ├─ levn@0.3.0
   │  │  ├─ prelude-ls@1.1.2
   │  │  └─ type-check@0.3.2
   │  │     └─ prelude-ls@1.1.2
   │  ├─ inquirer@0.12.0
   │  │  ├─ ansi-regex@2.1.1
   │  │  ├─ ansi-escapes@1.4.0
   │  │  ├─ cli-width@2.2.1
   │  │  ├─ chalk@1.1.3
   │  │  ├─ figures@1.7.0
   │  │  │  ├─ escape-string-regexp@1.0.5
   │  │  │  └─ object-assign@4.1.1
   │  │  ├─ readline2@1.0.1
   │  │  │  ├─ code-point-at@1.1.0
   │  │  │  ├─ is-fullwidth-code-point@1.0.0
   │  │  │  │  └─ number-is-nan@1.0.1
   │  │  │  └─ mute-stream@0.0.5
   │  │  ├─ run-async@0.1.0
   │  │  │  └─ once@1.4.0
   │  │  ├─ rx-lite@3.1.2
   │  │  ├─ lodash@4.17.21
   │  │  ├─ string-width@1.0.2
   │  │  │  ├─ code-point-at@1.1.0
   │  │  │  ├─ strip-ansi@3.0.1
   │  │  │  └─ is-fullwidth-code-point@1.0.0
   │  │  ├─ cli-cursor@1.0.2
   │  │  │  └─ restore-cursor@1.0.1
   │  │  │     ├─ onetime@1.1.0
   │  │  │     └─ exit-hook@1.1.1
   │  │  ├─ strip-ansi@3.0.1
   │  │  └─ through@2.3.8
   │  ├─ json-stable-stringify@1.0.1
   │  │  └─ jsonify@0.0.0
   │  ├─ js-yaml@3.14.1
   │  │  ├─ argparse@1.0.10
   │  │  │  └─ sprintf-js@1.0.3
   │  │  └─ esprima@4.0.1
   │  ├─ mkdirp@0.5.5
   │  │  └─ minimist@1.2.5
   │  ├─ lodash@4.17.21
   │  ├─ path-is-absolute@1.0.1
   │  ├─ optionator@0.8.3
   │  │  ├─ prelude-ls@1.1.2
   │  │  ├─ deep-is@0.1.3
   │  │  ├─ word-wrap@1.2.3
   │  │  ├─ levn@0.3.0
   │  │  ├─ type-check@0.3.2
   │  │  └─ fast-levenshtein@2.0.6
   │  ├─ is-my-json-valid@2.20.5
   │  │  ├─ generate-function@2.3.1
   │  │  │  └─ is-property@1.0.2
   │  │  ├─ xtend@4.0.2
   │  │  ├─ jsonpointer@4.1.0
   │  │  ├─ generate-object-property@1.2.0
   │  │  │  └─ is-property@1.0.2
   │  │  └─ is-my-ip-valid@1.0.0
   │  ├─ path-is-inside@1.0.2
   │  ├─ pluralize@1.2.1
   │  ├─ progress@1.1.8
   │  ├─ require-uncached@1.0.3
   │  │  ├─ caller-path@0.1.0
   │  │  │  └─ callsites@0.2.0
   │  │  └─ resolve-from@1.0.1
   │  ├─ shelljs@0.6.1
   │  ├─ user-home@2.0.0
   │  │  └─ os-homedir@1.0.2
   │  ├─ text-table@0.2.0
   │  ├─ strip-json-comments@1.0.4
   │  └─ table@3.8.3
   │     ├─ slice-ansi@0.0.4
   │     ├─ string-width@2.1.1
   │     │  ├─ is-fullwidth-code-point@2.0.0
   │     │  └─ strip-ansi@4.0.0
   │     │     └─ ansi-regex@3.0.0
   │     ├─ chalk@1.1.3
   │     ├─ lodash@4.17.21
   │     ├─ ajv-keywords@1.5.1
   │     └─ ajv@4.11.8
   │        ├─ json-stable-stringify@1.0.1
   │        └─ co@4.6.0
   ├─ istanbul@0.4.5
   │  ├─ async@1.5.2
   │  ├─ escodegen@1.8.1
   │  │  ├─ esutils@2.0.3
   │  │  ├─ estraverse@1.9.3
   │  │  ├─ esprima@2.7.3
   │  │  ├─ optionator@0.8.3
   │  │  └─ source-map@0.2.0
   │  │     └─ amdefine@1.0.1
   │  ├─ esprima@2.7.3
   │  ├─ glob@5.0.15
   │  │  ├─ inflight@1.0.6
   │  │  ├─ inherits@2.0.4
   │  │  ├─ path-is-absolute@1.0.1
   │  │  ├─ minimatch@3.0.4
   │  │  └─ once@1.4.0
   │  ├─ abbrev@1.0.9
   │  ├─ handlebars@4.7.7
   │  │  ├─ minimist@1.2.5
   │  │  ├─ neo-async@2.6.2
   │  │  ├─ wordwrap@1.0.0
   │  │  ├─ source-map@0.6.1
   │  │  └─ uglify-js@3.14.1
   │  ├─ js-yaml@3.14.1
   │  ├─ nopt@3.0.6
   │  │  └─ abbrev@1.1.1
   │  ├─ mkdirp@0.5.5
   │  ├─ once@1.4.0
   │  │  └─ wrappy@1.0.2
   │  ├─ which@1.3.1
   │  │  └─ isexe@2.0.0
   │  ├─ wordwrap@1.0.0
   │  ├─ supports-color@3.2.3
   │  │  └─ has-flag@1.0.0
   │  └─ resolve@1.1.7
   ├─ method-override@3.0.0
   │  ├─ methods@1.1.2
   │  ├─ vary@1.1.2
   │  ├─ parseurl@1.3.3
   │  └─ debug@3.1.0
   │     └─ ms@2.0.0
   ├─ hbs@4.0.4
   │  ├─ handlebars@4.0.14
   │  │  ├─ async@2.6.3
   │  │  │  └─ lodash@4.17.21
   │  │  ├─ optimist@0.6.1
   │  │  │  ├─ minimist@0.0.10
   │  │  │  └─ wordwrap@0.0.3
   │  │  ├─ source-map@0.6.1
   │  │  └─ uglify-js@3.14.1
   │  └─ walk@2.3.9
   │     └─ foreachasync@3.0.0
   ├─ marked@0.6.2
   ├─ morgan@1.9.1
   │  ├─ on-finished@2.3.0
   │  ├─ on-headers@1.0.2
   │  ├─ depd@1.1.2
   │  ├─ debug@2.6.9
   │  └─ basic-auth@2.0.1
   │     └─ safe-buffer@5.1.2
   ├─ mocha@5.2.0
   │  ├─ browser-stdout@1.3.1
   │  ├─ escape-string-regexp@1.0.5
   │  ├─ diff@3.5.0
   │  ├─ growl@1.10.5
   │  ├─ debug@3.1.0
   │  ├─ glob@7.1.2
   │  │  ├─ inherits@2.0.4
   │  │  ├─ fs.realpath@1.0.0
   │  │  ├─ inflight@1.0.6
   │  │  ├─ path-is-absolute@1.0.1
   │  │  ├─ once@1.4.0
   │  │  └─ minimatch@3.0.4
   │  ├─ commander@2.15.1
   │  ├─ minimatch@3.0.4
   │  │  └─ brace-expansion@1.1.11
   │  │     ├─ concat-map@0.0.1
   │  │     └─ balanced-match@1.0.2
   │  ├─ mkdirp@0.5.1
   │  │  └─ minimist@0.0.8
   │  ├─ supports-color@5.4.0
   │  │  └─ has-flag@3.0.0
   │  └─ he@1.1.1
   ├─ multiparty@4.2.1
   │  ├─ fd-slicer@1.1.0
   │  │  └─ pend@1.2.0
   │  ├─ safe-buffer@5.1.2
   │  ├─ http-errors@1.7.3
   │  └─ uid-safe@2.1.5
   ├─ pbkdf2-password@1.2.1
   │  └─ fastfall@1.5.1
   │     └─ reusify@1.0.4
   ├─ supertest@3.3.0
   │  ├─ methods@1.1.2
   │  └─ superagent@3.8.3
   │     ├─ component-emitter@1.3.0
   │     ├─ cookiejar@2.1.2
   │     ├─ extend@3.0.2
   │     ├─ form-data@2.5.1
   │     │  ├─ asynckit@0.4.0
   │     │  ├─ combined-stream@1.0.8
   │     │  │  └─ delayed-stream@1.0.0
   │     │  └─ mime-types@2.1.32
   │     ├─ debug@3.2.7
   │     │  └─ ms@2.1.3
   │     ├─ methods@1.1.2
   │     ├─ mime@1.6.0
   │     ├─ qs@6.10.1
   │     │  └─ side-channel@1.0.4
   │     │     ├─ get-intrinsic@1.1.1
   │     │     │  ├─ has@1.0.3
   │     │     │  │  └─ function-bind@1.1.1
   │     │     │  ├─ has-symbols@1.0.2
   │     │     │  └─ function-bind@1.1.1
   │     │     ├─ object-inspect@1.11.0
   │     │     └─ call-bind@1.0.2
   │     │        ├─ get-intrinsic@1.1.1
   │     │        └─ function-bind@1.1.1
   │     ├─ formidable@1.2.2
   │     └─ readable-stream@2.3.7
   ├─ should@13.2.3
   │  ├─ should-equal@2.0.0
   │  │  └─ should-type@1.4.0
   │  ├─ should-format@3.0.3
   │  │  ├─ should-type@1.4.0
   │  │  └─ should-type-adaptors@1.1.0
   │  ├─ should-type@1.4.0
   │  ├─ should-type-adaptors@1.1.0
   │  │  ├─ should-type@1.4.0
   │  │  └─ should-util@1.0.1
   │  └─ should-util@1.0.1
   └─ vhost@3.0.2

Tags

Steven

Steven has been writing software and exploring computers since the age of 17 all the way back in 2008!