Gem Crazy
May 15, 2010
Gem Crazy
I believe the Ruby community may have finally gone off the rails in it’s collective crazy train. I have been reading about Bundler, the spiffy new RubyGems manager to be default part of Rails 3.0, and I can’t help but wonder what track we were even on.
If you don’t know about Bundler yet, you can learn about it here. I imagine most of you are already aware of it and are now considering how it fits in with your future development plans. So I want so ask a more fundamental question: Why does Bundler even exist?
On the surface, we can of course thank Rails. The deployment of web applications
stands apart from the distribution of Ruby packages. Deploying a Rails
application usually means copying an app’s files up to a server, as opposed to
creating a distributable package file (like a gem) and installing that on
the remote machine. Technically the later could be done, but it isn’t, probably
because most web apps are proprietary and do not need wider distribution. Because
Rails developers do not use the normal RubyGems distribution mechanism, which
automatically installs dependencies, they need another way to ensure their apps
dependencies are present, hence the convenience of bundle install.
Bundler achieves this goal in a grand fashion. As its name implies
it does so by giving us the means to download the gems an application is dependent
upon and store them directly within the project itself. These gems get stored in
a project’s vendor/gems/ directory. The rationalized benefit being
that you need never worry about remote services going down (e.g. rubygems.org)
in order to deploy your application, nor ever worry about any potential changes
in a dependent gem. So in brief, one can bundle dependent gems with ones
application.
At first blush it seem like great idea. After all developers use vendoring to
do something similar while developing an application. But lets consider this
a bit more carefully. The worry of a remote service going down is, honestly, bordering
on paranoia. That’s why mirrors of RubyGems.org exist. If one goes down, the others
remain. The odds of all mirrors going down at the same time are extremely small, and
as you might imagine, such a situation would be corrected very quickly, if it ever did
occur. In fact, if it did happen, there is a fair chance your host will be down too.
So the solution here is not bundling, but redundancy. As for the fear
of “monkey releases” –new versions of a gem that that might break things,
this is somewhat understandable, but redistributing every dependency with each app
is shear overkill. If you are that concerned a gem will break an application you can
always specify exact requirements in the gemspec, and use the gem method to nail the
version down. That, in fact, is the whole point of the gem method. And it is a
perfectly good solution for applications such as a Rails app. Unfortunately is not
a very good solution for general purpose libraries. (As can be read about in any number
of blog posts by searching
Don’t require ‘rubygems’.)
[Update: I have been informed that the real benefit of bundling is to make it easier to install an application on machines that has limited Internet access. In such cases, I can see that Bundler might be handly.]
I should mention that the latest version of Bundler (v0.9) has a light-weight rendition
of bundling, called locking, which doesn’t actually store the gem in the application,
but automatically locks the gems your application uses to specific versions. In other
words, it generates a file which (I assume) will ulitmately be used with the gem method
to nail down the exact versions of gems your application is using at that time of the lock.
This is a much better approach than bloating your application with .gem files, to achieve
the same end, and to Bundler’s credit it makes it easy to keep those gem calls current.
However, it still ties your program to RubyGems, so again it’s not a perfect solution
for reusable libraries.
Another feature Bundler sets out to handle are groups. They are “gem environments”
which can be loaded as a collective, or even omitted during an install. In other words,
they are a way to specify a specific group of gems to the bundle command or in ones
code with Bundler.require. The most common groups you are likely to see are runtime
or production, test and development, patterned after Rails own database
environments. While I will not dismiss out-of-hand the potential of deriving some
utility from this feature, on the whole I suspect it is a YAGNI. Moreover,
it exposes the developer to even more potential deployment errors. I can only
imagine the failures that will arise because a testing environment
had a gem that the runtime environment forgot to add, but actually needed.
In addition, the use of Bundler promotes the use of Bundler.require. How many
countless hours have been spent reminding people not to put require 'rubygems'
in their lib code? Now the issue will be doubly compounded. While we can hope
that Bundler never makes it’s way beyond Rails (or at least end-user apps), I have
already seen it in at least two non-Rails programs.
Lastly, Bundler also provides an ability to depend on git repos, so long as
they track a .gemspec. This feature is a clear address to vendoring, but
via gem dependencies. I won’t even go into all the issues that arise with this
(and seems to somewhat contradict Bundler’s primary purpose –or at least compound its need!).
You can read some of the back and forth on this idea here.
I will say, however, what really strikes me most about this is
how an unproven system takes on such fanfare that it’s proponents now declare we have
all been doing it wrong! A .gemspec should never be generated by some “extraneous
build tool”, but ought to be edited by hand and checked into the scm repository. Wow.
I realized the Bundler is addressing certain needs, primarily of the Rails community.
The problem with Bundler is that it is trying to correct something that is fundamentally
problematic from the start. On two fronts… The first being that RubyGems ties
a packaging system to versioning, so using the gem command will never be a universally
applicable solution to lock down versions. And the second, more significant issue, that RubyGems,
and hence Bundler, cannot avoid version conflicts. RubyGems blows-up when it
encounters a conflict. Bundler only mitigates this issue by selectin suiable version before
runtime. But niether allow different versions to run simultatneously. This is the real heart
of the matter, and it stems from the fundamental lack of version support in Ruby itself.
In my opinion, until that is addressed, all of these fancy solutions are simply trains headed
up the wrong track.
title : Gem Crazy author : trans categories : [ruby, rubygems] date : 2010-05-15 layout : post