The package dependency graph

Most programming languages have some kind of package manager. Our example will be RubyGems, which manages Ruby packages called gems.

Among many other features, RubyGems allows gems to specify dependencies. Gems can depend on other gems depending on yet more gems, just as functions can depend on other functions depending on yet more functions. As with functions, this forms a graph – the package dependency graph.

We'll use the Rails gem as an example to explore packaging. It's a large and stable package, with a nontrivial but still tractable dependency graph.

As this article is written, the current version of Rails is 5.0.2. There are 11 explicit dependencies, 10 of which are owned by the Rails organization itself. Many of those packages have their own dependencies, with 38 total packages in the dependency graph.

Rails 5.0.2's full dependency graph is shown below. Nodes with solid edges are gems owned by the Rails organization on GitHub; dashed nodes are third-party gems. There's no need to read through this exhaustively, but do make note of the one node whose connectedness stands out.

The activesupport gem is a clear outlier in this dependency graph, like User is in most web applications' module graphs. Rails developers will know the reason: activesupport is a junk drawer of useful utilities, mostly extensions to the Ruby standard library. It's heavily used by Rails itself, applications using Rails, and even other libraries unrelated to Rails.

For the internal design of systems, an ubernode like this is widely considered bad design. In packaging, the situation is less clear; there's little consensus about any property of the package graph.

This is one section of The Programmer's Compendium's article on Software Structure, which contains more details and context.