« Ruby, Rails, and TextMate | Main | It's Oscar Season... »

November 11, 2004

Transparent Inversion of Control

The dynamic nature of languages such as Ruby make integrating facilities such as Inversion of Control both painless and transparent (and not a line of XML in sight).

Recently, the concepts of Dependency Inversion (DI) and Inversion of Control (IOC) have been gaining mindshare, particularly in the Java world. Implementations such as Spring and Pico are becoming popular.

In implementation terms, DI is a very simple technique. Rather than have a monolithic application that knits itself together explicitly, a DI application is written as a set of loosely coupled components. These components contain no knitting code: nothing in the application code itself is responsible, for example, for making sure that the necessary objects somehow get an instance of the database connection. Instead, the components all run in a container. This container is given a description of the knitting to be done (typically using an XML file). The container then instantiates objects and sets them into components that need them.

This is useful for a number of reasons, but is essence they all boil down to the same thing: the relationship between the components in our application is now abstracted out of the application. It’s a move towards the idea of component-based development and Brad Cox’s Software ICs.

DI and Ruby.

Let’s imagine that we were developing an application using Ruby on Rails. It’s an MVC framework, so we’ll have models that access our database, and we’ll have controllers that orchestrate the flow through the application. The controller code will make use of facilities in the various model classes. For example, the action to display a list of customers in a given city might look like:

   def list_customers_in_city
      @customers = Customer.in_city(@params['city'])
   end

Our user might click on a particular customer to get their order details.

   def find_customer_orders
      customer = Customer.find(@params['id'])
      @orders  = customer.list_of_orders
   end

Notice how we’re using class methods to do table-level things (such as searching) and instance methods to operate on a particular customer.

So now let’s imagine that we want to test this application (I know, it’s a stretch, but play along). Rails already comes with a neat test framework that lets me set up test data in a fairly automatic way, but it still uses a test database. Maybe I don’t want to do that. Maybe instead I want to use a mock version of my Customer class instead.

DI to the rescue. The are a number of DI frameworks for Ruby (such as Needle). These would let me set objects as attributes of my controller. In Ruby, of course, classes are themselves objects. I could change my controller to make the customer model an attribute.

   class MyController < ...
      attr_accessor :cust_model

      def list_customers_in_city
         @customers = @cust_model.in_city(@params['city'])
      end

      def find_customer_orders
         customer = @cust_model.find(@params['id'])
         @orders  = customer.list_of_orders
      end
   end

I’d then use Needle to store either a real or a mock Customer class into the cust_model attribute. My controller would switch between the two without knowing anything about it.

Dynamic Constants

Discussing this with David Heinemeier Hansson, it seemed to me that we could do better than this.

One of the problems I have with the current IOC implementations is the "then a miracle happens" stuff. You’re looking at the source to a component, and you don’t really know how it is set up. In the example above, you assume that somehow the cust_model attribute is set to a class that implements a customer model, but it isn’t obvious. The same is true in Spring and friends—a whole lot of stuff happens behind the scenes, and some of that stuff is important.

So I suggested to David that we could make Rails more explicit. Rather than having some random instance variable containing the model’s class, we could declare that such-and-such a class represents a model that the controller is using, but have that class actually injected by the framework. And through the miracle that is Open Source, David mentioned it to Jamis Buck, and Jamis added the feature to his DI Framework for Rails (and all in less than a day).

The upshot? You will be able to rewrite this code as:

   class MyController < ...
      model :customer

      def list_customers_in_city
         @customers = Customer.in_city(@params['city'])
      end

      def find_customer_orders
         customer = Customer.find(@params['id'])
         @orders  = customer.list_of_orders
      end
   end

Now we’re back to using the class name of the model in the main body of the code. We’ve also made explicit that the Customer is a model by saying model :customer (Rails does name mangling, so the name of the model is automatically capitalized). If we are not running our app under a DI framework, the story ends there—our controller uses the standard Customer class.

However, should we choose to run using Jamis’ framework, then things get more interesting. The call to

  model :customer

no longer statically loads up the file model/customer.rb. Instead, it consults Needle’s registry, and finds out what class has actually nominated itself as a model for customers. It then sets the constant Customer in the controller to reference that class. The net result is that the controller class declares that it uses a model, and then simply uses that model by name. But which implementation of that model gets used is determined by Needle configuration at runtime.

I think this is starting to get promising, and again illustrates the power and expressiveness of a more dynamic approach to programming.

TrackBack

TrackBack URL for this entry:
http://www.typepad.com/t/trackback/2226312/7670520

Listed below are links to weblogs that reference Transparent Inversion of Control:

Comments

Post a comment

If you have a TypeKey or TypePad account, please Sign In

Now in Beta

  • Programming Ruby, 3rd Edition
    Third Edition, Covering Ruby 1.9, now in beta
My Photo

Pragmatic Stuff

Photos

  • www.flickr.com
    This is a Flickr badge showing public photos from pragdave tagged with pragdave_badge. Make your own badge here.

Site Search

  • Google Search

    The web
    PragDave