We're hiring!

We're actively seeking designers and developers for our all three of our locations.

Conject Now Supports Lazy Dependency Resolution

Conject, my dependency injection tool for Ruby, has seen a handful of small revisions since I last blogged about it in May 2012. The current release (0.1.0) has a few new features that you might find useful:

  • Lazy, on-demand dependency resolution as an optional alternative to constructor injection.
  • All objects in the Ruby runtime may now access their context via the #object_context accessor.
  • You can now omit module-name prefixes when listing dependencies in construct_with and provide_with_objects for objects in the same module.

Lazy Dependency Resolution: Alternative to construct_with

The construct_with method is specifically designed to imply that your object must be constructed with a specific set of objects from the context within which it is being constructed. This is sometimes called “constructor injection”; an object’s dependencies are resolved before the object is constructed, and it may not be constructed without them. If your design says, “a car is built with an engine and a chassis,” first an Engine is constructed, then a Chassis, and those instances are passed as arguments to the constructor of a new Car instance.

This approach to object composition gives you a lot of expedience, clarity, and convenience… if you’re in charge of the design of your objects and you’re able to hold to a consistent class/object naming convention. Conject can do most of the heavy lifting by recursively building up your object graph without configuration, and all you need to do is ask for your objects by name.

But sometimes you’re not in a good position to support constructor-time injection, because you’re not in charge of how or when some of your objects are constructed. Some frameworks (such as Rails) use inheritance and automated factories to build objects for you. (You may write the methods for your Rails Controllers, but you don’t usually write the code that instantiates them… so, neither will Conject). construct_with won’t do you much good if you can’t get Conject to build your object for you.

Enter provide_with_objects, which enables your object to refer to its named dependencies in the same way you would if you’d used construct_with, but doesn’t require that you let Conject build the object for you. Dependencies are resolved when your code actually attempts to use them, pulled from your object’s context when you invoke one of your internal accessors.

# Constructor-time injection:
class MessageHandler
  construct_with :parser, :broker
 
  def handle(data)
    broker.dispatch parser.parse(data)
  end
end
 
# Lazy dependency provision:
class MessageHandlerActor
  include Celluloid
 
  provide_with_objects :parser, :broker
 
  def handle(data)
    broker.dispatch parser.parse(data)
  end
end

direct_vs_indirect

In both examples, a message handler uses a parser and broker to handle incoming messages. However, the second example is using Celluloid (a cool actor library for Ruby) which works some of its own subtle magic on your object’s constructor, in such a way that construct_with won’t function properly. However, provide_with_objects generates a parser accessor, which will pull the necessary object from the message handler’s context on first use.

Furthermore, you can use provide_with_objects within a Class:

class MessageHandler
  class << self
    provide_with_objects :parser, :broker
  end
 
  def self.handle(data)
    broker.dispatch parser.parse(data)
  end
end

To summarize how provide_with_objects differs from construct_with:

  • It does not interfere with a class’s #new or #initialize methods.
  • Dependencies are not resolved at object creation time.
  • Dependencies that are never referenced are never resolved.

All Objects now Have #object_context

An object will now retain knowledge of the ObjectContext within which it was built. If the object wasn’t built by Conject, but manually put into an ObjectContext, object_context will refer to that context. If the object hasn’t been anywhere near Conject, object_context will reference Conject.default_object_context. All Class assume the context of Conject.default_object_context.

Module-Relative Object Names

When listing dependencies in “construct_with” and “provide_with_objects,” you can now (optionally) omit the module prefix portion of object names if they exist within the same Module. This is convenient when you’re building up namespaces for your code, but find yourself constantly, needlessly repeating the module name when referring to other objects. In cases where you don’t really need or want to see the fully qualified object name, this can be really nice.

class Sports::Baseball::Players; end
 
cjlass Sports::Baseball::Field; end
 
# Using canonically named objects:
class Sports::Baseball::Game
  construct_with "sports/baseball/players", "sports/baseball/field"
end
 
#...or using module-relative object names:
class Sports::Baseball::Game
  construct_with :players, :game
end

The mechanism that translates your object names into class names will only consider the module of the declaring object (in this case, Baseball). There’s no magic (yet?) for walking back up the tree, so if you try construct_with :announcer within the Sports::Game::Baseball class, Conject won’t find Sports::Announcer.

Conject will continue to improve, change, and grow as we find more patterns of use for it. If you’ve got any suggestions of your own, file a feature request on the Conject Github page!
 

This entry was posted in Ruby and tagged , . Bookmark the permalink. Both comments and trackbacks are currently closed.