Wednesday, January 24, 2007

Gem-based Rails Dependency Loading: Part 1 - Justification

Part of the Gem-based Development Process series


Quickly, how plugins work:

For each plugin, in alphabetical order:

  1. Paths are added to the load order (i.e. lib)
  2. Initialization routines are run (init.rb)

Quickly, how RubyGems work (in comparison):
  1. Addition of paths to the Ruby require path ($:.unshift)
  2. Autorequire of a file within this new require path for 'initialization'
  3. Activation of dependencies--Rubygems will recursively activate (i.e. perform steps 1 and 2) all dependencies (and sub-dependencies) before loading the gem proper. You don't have to worry dependencies failing to be in the load path (or failing to run their initialization routine)
Justification 1: Dependencies

So long as each gem's first order dependencies are properly 'recorded' in the gemspec, you can load your gems in any order you desire. This relieves the need to untangle webs of dependencies and do crazy hacks such as:

  1. Renaming plugins to effect load order
  2. Bulk-adding directories to the load path in order to 'cheat' the load order (which can work initially, but ultimately fails since some plugins can be dependent on the actual hooks installed by another plugin's init script)

Justification 2: Maintenance versioning

If you've got multiple applications sharing your plugin, it can be awkward to maintain under the standard plugin model. You're going to want to continue development on the plugin, but you don't want to force all clients to be on 'trunk' (or force all clients to move to trunk to pick up critical bugfixes.)

With this in mind, you're going to need the ability to backport bugfixes to maintenance branches (and provide a mechanism for clients to absorb these backported fixes without absorbing unwanted non-backwards-compatible changes.)

With RubyGems, client applications need only use the right optimistic version lock, and they'll pick up critical fixes on their minor (or major) revision without committing to a boatload of unwelcome API changes.

We're very wary about overengineering our solutions, so keep in mind that things begin their life as plugins (and continue to work as usual.) We just take care to gem-up things that are not well suited for plugins (e.g. highly cross-dependent or highly versioned.)

Next time: How we made it work

No comments: