Chad Fowler wrote: > For those not subscribed to RubyGarden's rss feed[1], Jamis Buck has > written a new feature piece discussing his journey from > dependency-injection-novice to two-time-IoC-frameworker. He also > discusses the (unusual) decision to start over on a framework that is > less than a year old and demonstrates the differences between these > two frameworks (Copland and Needle). Big thanks go to Jamis for his > fascinating contribution. > > The article is available on RubyGarden's front page or at: > http://rubygarden.org/index.cgi/Libraries/copland-to-needle.rdoc > > Request: If you'd like to write an article or tutorial or conduct an > interview to be published on RubyGarden.org, please send me an email > directly. I'm working to establish a rhythm of new content releases, > so if you're interested in contributing I would love to hear from you. > > Thanks, > Chad Fowler > > [1] http://rubygarden.org/index.cgi/index.rss Jamis Buck's and Jim Weirich's articles are great. I could never get through the Java-oriented discussion of this subject, and now that the subject is presented for rubyists a dim bulb is finally going on in my head. What really helps me think about this is to reduce it to some simple, standard Ruby idioms, which don't provide as many features as Needle, but are close enough to code that I've actually written to make it all seem meaningful. Let's take Jim's example[1], which is clear, rubified, and explicit about DI constructs, and translate it into some concrete, mundane ruby code without those constructs. Here's the original: def create_application container = DI::Container.new container.register(:logfilename) { "logfile.log" } container.register(:db_user) { "jim" } container.register(:db_password) { "secret" } container.register(:dbi_string) { "DBI:Pg:example_data" } container.register(:app) { |c| app = WebApp.new(c.quotes, c.authenticator, c.database) app.logger = c.logger app.set_error_handler c.error_handler app } container.register(:quotes) { |c| StockQuotes.new(c.error_handler, c.logger) } container.register(:authenticator) { |c| Authenticator.new(c.database, c.logger, c.error_handler) } container.register(:database) { |c| DBI.connect(c.dbi_string, c.db_user, c.db_password) } container.register(:logger) { |c| Logger.new(c.logfilename) } container.register(:error_handler) { |c| errh = ErrorHandler.new errh.logger = c.logger errh } end And here's a rewrite that functions in about the same way, but without being explicit about containers and services. class Application def logfilename @logfilename ||= "logfile.log" end def db_user @db_user ||= "jim" end def db_password @db_password ||= "secret" end def dbi_string @dbi_string ||= "DBI:Pg:example_data" end def app @app ||= WebApp.new(quotes, authenticator, database) end def quotes @quotes ||= StockQuotes.new(error_handler, logger) end def authenticator @authenticator ||= Authenticator.new(database, logger, error_handler) end def database @database ||= DBI.connect(dbi_string, db_user, db_password) end def logger @logger ||= Logger.new(logfilename) end def error_handler @errh ||= ( ErrorHandler.new @errh.logger = logger @errh ) end end def create_application Application.new end So we're using the Application class itself as the container, and we're using the instance methods of this class as the service points. The methods return the services, and also store them in instance variables. A service is registered using "def". Instead of using the block parameter to pass the container to the service definitions, you just use self. The familiar ||= idiom provides the singleton service model. It's quick and dirty, and it obscures the conceptual structure, but if you're familiar with ruby, you can see immediately what is going on. You've probably even written code a little like this. Why would you want to "upgrade" your code to a more explicit form of DI? There are several disadvantages to this implicit DI style, aside from the implicitness itself: - It doesn't help much with things like interceptors, although you could bring in your favorite AOP library. - It mixes your service namespace with the namespace inherited from Object and Kernel: maybe you want a service called "dup" or "puts", but then you cannot call the Object and Kernel implementation of these methods from within methods of the Application class. - Reflection must be done using standard ruby reflection on classes, so you have to, for example, filter out methods that are not really services. OTOH, you can use class inheritance and module inclusion to build trees of container definitions in a very natural and familiar way, emulating some of the functionality of Needle: module LoggingServices def logger; ...; end end module GUIServices end module DatabaseServices end class MyServiceContainer include LoggingServices include GUIServices include DatabaseServices def app MyApp.new(logger, gui, database) end end The other service models besides the singleton model are also easy to implement with quick and dirty ruby code: - Threaded: def per_thread_logger @per_thread_logger ||= {} @per_thread_logger[Thread.current] ||= Logger.new end - Prototype: def gui_button MyButtonClass.new end - Deferred (ok, this one gets a little messy, and the details should be abstracted away by some fancy metaprogramming): def big_resource @big_resource || big_resource_proxy end def big_resource_proxy @big_resource_proxy ||= ( proxy = [proc {|br| @big_resource = br}] def proxy.method_missing(*args, &block) big_resource = BigResource.new at(0).call(big_resource) big_resource.send(*args, &block) end proxy ) end private :big_resource_proxy There are other service models that are very easy to construct with methods, but I'm not aware of an equivalent construct in Needle: def printer(kind) @printers ||= {} @printers[kind] = Printer.new(kind) end def printer_for_code_files @printer_for_code_files ||= printer(:monochrome) end def printer_for_images @printer_for_images ||= printer(:color) end It might be possible to do the above in Needle using Pipelines.[2] Namespaces, if I understand the Needle docs correctly, work something like this: class PictureApp class ColorNamespace def red; PrimaryColor(:red); end def green; PrimaryColor(:green); end def yellow; @yellow ||= red+green; end end def colors @colors ||= ColorNamespace.new end def picture @picture ||= Picture.new(:background => colors.yellow) end end Alternately, if you want color instances to shared by all PictureApps, you might want to define the colors service like this: def colors @@colors ||= ColorNamespace.new end I hope there aren't too many inaccuracies in the above, and that this helps other folks move from older ruby idioms to the new idioms that Needle gives us. -- [1] http://onestepback.org/index.cgi/Tech/Ruby/DependencyInjectionInRuby.rdoc [2] Anybody know? Or are parameterized services a misuse of the DI pattern?