{ |one, step, back| } 90 to 99 of 182 articles WikiSyndicate: full/short

Pennsylvania Dutch   20 Jan 05
[ print link all ]

Eckel on Ruby

Recently Bruce Eckel addresses the "Ruby Issue" yet again. Evidently a number of years ago Bruce brushed off Ruby based on a quick perusual of a Ruby book. Ever since then, the Ruby community has been trying to convert him, finding it hard to believe that someone as intelligent and well spoken as Bruce didn’t immediately fall in love with our little language.

In the article, he says:

Look at one of the first examples from "Why’s Poigniant Guide," where he’s asserting that Ruby is "the language of our thoughts":
  5.times { print "Odelay!" }

Or this:

  exit unless "restaurant".include? "aura"

This makes sense if you used to be a Smalltalk (or perhaps Forth) programmer, and I know one who started with Python and has moved to Ruby. It also makes sense if you grew up Pennsylvania Dutch, where they say things like "Throw Papa down the stairs his hat," and "Throw the horse over the fence some hay."

Well that explains everything! You see, my father was Amish (until he was about 13 years old) and my mother’s parents were Amish. The Amish speak Pennsylvania Dutch (a distant dialect of German) in their homes and use High German in their church services. Often Amish children will enter first grade before they learn to speak english.

So I grew up in a community where Pennsylvania Dutch was often spoken, and influenced the way english was spoken. This "backwards" way of speaking sounds very familiar to me.

No wonder I am so attracted to the Ruby language.

An Aside

I do find it humorous that Bruce is (mildly) poking fun at the Ruby (and Perl) technique of using if and unless as statement modifiers. Bruce makes it sound as if putting the if condition at the end of a statement (e.g. return if data.nil?) is somehow awkward english.

Now go reread the first two sentences of the paragraph that begins "This makes sense if …".

Interesting.

Coming Around?

Actually, from the tone of the article (and some of the later comments), I get the feeling that Bruce respects that fact that a lot of people find Ruby attractive and productive.

Its just that it doesn’t click for him.

That’s OK. As my Amish relatives would say, "It takes all kinds, the world to make".

Increase Your Ruby Skills

If you really want to increase your Ruby skills, I recommend that you spend a good part of the day listening to John Schmid sing a few Amish folk songs. My favorite is "Maydly vit du hayra?" where the Amish father queries his young daughter on the type of man she wants to marry. I actually remember the song "Reide, Reide, Geile" (Ride, Ride the Horse) sung in my house while growing up (usually as we were bouncing on Dad’s leg). Good Stuff!


Web Applications   20 Jan 05
[ print link all ]
I am really suprised at the number of web applications that I have started using in just the past few months. I’ve never been a fan of web apps (too slow, clusmy interfaces, etc.), but GMail broke the mold here and showed the world what a good web interface could do. And I think we will see more of that as time goes on.

Anyways, here’s what I use…

BlogLines (bloglines.com)
After messing around with desktop news aggregators for a while, I moved to BlogLines to monitor all my news feeds. It solved two problems for me: (1) the need for a cross platform news aggregator and (2) keeping multiple workstations synced with regard to the news already read. This is the perfect fit for web app.
GMail (gmail.com)
I entered the GMail race late in the game, as I was very happy with my mail hosting on the UML Co-op system that I use. However I discovered one problem with hosting mail on the co-op box. When the box goes down, how do I send/receive mail on the UMLCOOP mailing list? So I broke down a got a GMail account mainly for backup. I’ve very impressed with the clean and responsive user interface. GMail has set the bar for all web apps in the future.
del.icio.us (del.icio.us)
Yes, that’s how its spelled, and yes, that is really the host name. Weird. But I love this site. I had heard about it for some time and never really understood what it was all about. Then two months ago someone demoed del.icio.us at our local Linux users group meeting. Since then I’ve got over 400 bookmarks added to the system.

So, what is del.icio.us? It is a bookmarking web app that allows you to add keywords to the bookmarks. For example, if I come across a great site about programming X10 devices in Ruby, I can bookmark the site and associate the keywords "ruby", "x10", "programming" with the bookmark. Later I can come back and ask for all bookmarks associated with "x10". Cool! Did you ever want to return to a site you had visited earlier, but just can’t remember where in web it was? Del.icio.us is a great answer to that.

And what’s more, your bookmarks are sharable. Today someone ask me about Ruby IDE’s and I sent them to my del.icio.us bookmarks: del.icio.us/jimweirich/ide+ruby

Ta-Da Lists (www.tadalist.com)
I just came across this one today, and its the real reason I started this blog entry. Wow, what a simple idea. And so beautifully executed. Notice the lack of submit buttons. Just start typing todo list entries, hitting a return to go to the next one. Finished a todo item and want to check it off? Just check the box … no submit button needed to get the changes back to the database. Ta-Da lists uses XMLHttpRequest to interact dynamically with the host. Beautiful.

And to top it off, it is a Ruby-on-Rails application. Written in 579 lines of Ruby code, that’s less than the size of the XML config files used in many J2EE applications. David is really showing off the latest features of Rails too.

Now, if I only liked todo lists …


Ron Jeffries is Looking ...   20 Jan 05
[ print link all ]
It seems that Ron Jeffries is looking for a little help on his web site. One of the desirable skills listed is Ruby. He’s not promising to pay much, but hey, you get to use Ruby.

See www.xprogramming.com/Blog/Page.aspx?display=LittleHelp for details.

XP Cincy Makes a Surprise Move   17 Jan 05
[ print link all ]
It is funny how surprises happen when you least expect them.

XP in Cincinnati

The Cincinnati XP Users group meets on the first tuesday of every month. At the meetings we "practice" our XP skills by pairing and working a small web project for Childrens Hospital. The project is a simple scheduler for reserving rooms.

Most of the meetings we have about 4 or 5 pairs of java programmers and one pair of Ruby programmers (the Ruby pair is usually me and Chris). Working on the project for only a few hours a month means that very little gets done at the meeting, but that’s ok because the meeting is more about learning the process that getting software delivered.

But working on the same project for over a year is getting a bit old, and the group was looking for something new to work on.

The January Meeting

I didn’t make the January meeting, so I only got this news second hand. Here is a portion of the meeting summary that Mark Windholtz published. As you read this, keep in mind that the group has been exposed to Ruby over the past year, but only a couple of them had actually paired with me and had direct exposure to the language.

So what’s next for the group? We talked about goals. do we want to …
  1. focus on delivery?
  2. focus on experimentation (with an eye toward adding buzz words to our resumes)?
  3. do something wacky and fun?
  4. something else …?

We kicked that around a little.

Bill suggested that they were not exclusive goals and said we could have fun while delivering.

Somehow we started talking about PHP and Rails. We watched a 10 min Rails video that took 12 minutes to download on the slow line. (you gotta see this!)

media.nextangle.com/rails/rails_setup.mov

If you have not seen the Ruby on Rails 10 minute demo movie, it is worth downloading and watching. Really, it is only 10 minutes (not counting download time).

Continuing with Mark’s summary.…

It blew us away how easy it was to build a minimal web app in 10 minutes.

After the video Bill announced that he had a Rails server installed and running.

Gerard commented that it took us 2-3 meetings to get Tomcat configured for peoples PCs, While it took Bill about 20 minutes to get a page showing in an unfamiliar technology.

Skipping over some discussion stuff …

So it turned out unanimous that we would load-up on Rails and next month start developing something. […] I think this is going to be huge amounts of fun.

I think its going to be fun too. I can’t wait until the next meeting. That’s a big part of the Ruby attraction. It puts the fun back in programming.

Invitation

If you are interested in Ruby, either a beginner or an expert, feel free to drop by the next Cincy-XP Users Group meeting on February 1. See www.objectwind.com/cgi-bin/wiki.cgi?MeetingTimeAndLocation if you need help finding us.


Slowing Down Calculations   08 Dec 04
[ print link all ]
Normally you want your calculations to go as fast as possible. But not this time. Here we will look at how to slow them down. Slow them waaay down.

Time for Some Fun

If you are following the Ruby mailing list, you will see we just made a new release of RubyGems. For the past week, most of my spare time has gone into making that happen. Now its time to get back into some other projects that I’m woefully behind on …

But before we do, it’s time to have a little fun with Ruby.

A Language Based SpreadSheet

Patrick Logan wonders aloud why spread sheets haven’t made it into programming languages as first class objects. He then goes on to speculate what it might look like in Smalltalk. And along the way, he bumps into some interesting concepts about evaluating expressions.

Translating Patrick’s Smalltalk code into Ruby turns out to be quite straight forward …

  ss = SpreadSheet.new
  ss[1,1].value = 5
  ss[1,2].value = ss[1,1] * 6
  ss[1,3].value = ss[1,2] * 7
  puts ss[1,3].value             # => 210

ss is our spread sheet object. Indexing it with row and column indicies yields cells, which can hold formulas. The first cell gets a five. The second cell (at [1,,2]) gets 6 times that (30), and the final cell gets 7 times that (210).

Straight forward arithmetic, right?

Wrong!

Remember that this is a spread sheet. Changing the values in cells should effect cells that derive from it. In other words, if we change the value of ss[1,1] and then ask for the value of ss[1,3], we should get a new value.

  ss[1,1].value = 10
  puts ss[1,3].value     # Should now print 420!

The naive implementation of just storing values in arrays as we calculate them just isn’t going to hack it.

Ummm … Why Don’t We Use Lambdas?

My first thought was that we would want to just wrap the expressions in a lambda. Putting them in a lambda has the effect of deferring the evaluation of the expression until we really want it. And we can reevaluate the expression as needed as its dependents change.

The result would look like this …

  ss = SpreadSheet.new
  ss[1,1].value = 5
  ss[1,2].value = lambda { ss[1,1] * 6 }
  ss[1,3].value = lambda { ss[1,2] * 7 }
  puts ss[1,3].value             # => 210

In fact, a commenter to Patrick’s blog suggested the same thing.

Patrick responds.

The problem then is the programmer has to know where to put the blocks to delay computation and where to force the revaluations. Instead of blocks, though, my intention is to use some new object, a Formula, say, and to make those objects implicit as much as possible.

Deferring Expressions in Ruby

How would this work out in Ruby? In Ruby, all computation is accomplished by sending messages. To defer a computation, just capture its message and replay it later at your convenience. We will start with a formula object that turns any message sent to it into a deferred object.

  class Formula < Builder::BlankSlate
    def method_missing(sym, *args, &block)
      Deferred.new(self, sym, args, block)
    end
  end

Since you generally want every message sent to a formula object to be recorded, I based Formula on the BlankSlate class I’ve mentioned earlier. Deferred is also fairly straight forward to write. The initialize method just records the receiver of a message, the message name, arguments and any blocks.

  class Deferred < Formula
    def initialize(target, operation, args, block)
      @target = target
      @operation = operation
      @args = args
      @block = block
    end
    # ...

We derive Deferred from Formula because we want operations against a deferred object to also be deferred, and Formula handles that perfectly.

Now, when we want the value of a deferred operation, we need to ask it nicely. We will use a method named formula_value for that purpose.

    def formula_value
      @target.formula_value.send(@operation, *eval_args, &@block)
    end
    # ...

To replay the deferred operation, we need to get the target (reciever) of the message. Since the target was a formula, we need to ask for its formula value. We then send it the original operation (message name) and a list of arguments. Just one subtle note about the arguments. They too might (or might not) be formula objects, in which case we need to evaluate them before passing them to send. eval_args just creates a new list of formula values from the original argument list.

    def eval_args
      @args.collect { |a| a.formula_value }
    end
  end  # of class Deferred

Formulas in Action

Suppose I had a Formula object, and tried to add 1 to it. What would I get …

  result = some_formula + 1

result is a new deferred object, capturing the sending of +1 to the formula. To get the real value, we ask the result for its formula_value:

  result.formula_value

which recursively gets the formula value of some_formula and sends it a +1 message.

Something is Missing

Great. We see how to get deferred evaluations if we have a formula object, but where does that original formula object come from?

There are several options. One is to create a Formula wrapper around a normal Ruby value.

  class Const < Formula
    attr_reader :formula_value
    def initialize(value)
      @formula_value = value
    end
  end

Now we can say:

  result = Const.new(5) + 3    # Returns a deferred formula object

and later ask:

  result.formula_value         # Returns 8

Mirror, Mirror

Another question: I can see how formula+1 works. The + message is sent to a formula object and gets handled specially. But what if we write:

  1 + formula

Won’t 1 get confused because it doesn’t know how to handle a formula object?

Well, yes and no. The + operation in Integer will get confused, but it has a backup procedure to follow when it doesn’t know how to add itself to an arbitrary object: It asks the object how to do it.

Integer + will call coerce on the Formula object and expect Formula to figure out how to do the addition. Coerce should return a two element list whose elements can be added together.

Here is coerce for Formula:

  class Formula
    def coerce(other)
      [Const.new(other), self]
    end
  end

Formula just wraps the original number in a Formula wrapper and then lets the wrapper handle deferring the addition.

The SpreadSheet Cells

The Cells of the spreadsheet are Formula objects too. The only catch here is that they can hold different Formula objects over time. value should calculate the value of a cell (and will in turn call formula_value to do so). value=(val) should set the internal formula to whatever the user specifies.

Here is Cell in all its glory.

  class Cell < Formula
    def initialize
      @formula = 0
    end

    def value
      @formula.formula_value
    end

    def value=(new_value)
      @formula = new_value
    end

    def formula_value
      @formula.formula_value
    end
  end

Making a Cell object a formula is pretty key to the whole deferred calculation design. If a cell contains only normal Ruby objects, then its value can be calculated immediately. However, we want to defer calculations that depend upon other cells, and since adding a cell object to any expression will automatically defer it, we get the effect we want automatically.

Hey! Wait a Minute!

The astute observer will note that I initialize @formula in Cell to 0, but later in the code send a formula_value message to @formula. Won’t this cause problem, sending formula message to a non-formula object?

Well, yes it would. But it simplifies the code greatly if we just pretend all objects are formulas and can respond to formula_value. Non-formula objects just respond by returning themselves.

This is easy to accomplish.

  module Kernel
    def formula_value
      self
    end
  end

(And this is why I chose a rather awkward name for fetching a formula’s value. Since all objects will implement this method, I wanted it to avoid potential name conflicts).

The SpreadSheet

The final piece of the puzzle is the actual spread sheet object. Its only real function is to act as a lookup container for the Cell objects. My implementation uses a cheesy "convert two integers to a string key" technique. It works ok, but it bothers me.

  class SpreadSheet
    def initialize
      @hash = Hash.new
    end

    def [](r,c)
      @hash["#{r}@#{c}"] ||= Cell.new
    end
  end

That’s it. The guts of a spread sheet in under 100 lines of code. You can see the complete listing on my wiki.

Other possibilities

This technique of deferring calculations has some really interesting possibilities. Imagine that instead of replaying deferred calculations, we examined the deferred expression and performed some operation on them. Like what? Well, maybe optimizing the expression, maybe analyzing the variable references, maybe generating SQL from them. Remember the Criteria library … it uses this technique to translate ruby code into SQL conditions.

Limitations

Be careful with this technique. Although quite powerful, there are a couple of places that can cause problems. In particular, the && and || operators in Ruby are difficult to capture in this way.


Geek Birthday Humor   20 Nov 04
[ print link all ]
I’ve had a lot of fun with my birthday this year. I tell people that …
I’m going to turn 30 this year.

Then I pause and let them do a double take at the graying color of my hair, and then finish with …

… hexadecimal.

The joke goes over pretty well amongst my geek and programmer friends. However, when used in non-geek company, they just look at me as if I lost my mind.

Perhaps I have.

Ruby Poetry   18 Nov 04
[ print link all ]
Robert Cottrell has captured the essence of Ruby in haiku form:
a Ruby program
before I even notice
is already done

These are almost the exact words of one of my coworkers, who after considering Java for a particular problem, decided to use Ruby instead. He said he just started writing a little bit of code and all of a sudden he realized that he was done.

Dependency Injection Revisited   29 Oct 04
[ print link all ]
In a blog entry, Jamis Buck points out that Needle (a dependency injection framework based on some of my random thoughts) is a superset of the Service Locator pattern.

This is true in general. Both the DI and SL patterns provide containers that you pull objects out of. However, the essential difference between them is that your application code is aware of the Service Locator pattern, but Dependency Injection is invisible to the app.

So, if you take your DI container and pass it to a constructor, you are really using the DI container in a Service Locator manner. The key question to ask is: If you were to change the interface to the DI container, does your app need to change as well.

Someone else asked me how to decide between DI and SL, or just rolling your own. My answer is to look at the amount of pain you suffer on configuring your the modules[1] of your system. Ruby is flexible enough so that most projects can get by without either DI or SL. When it begins to get cumbersome managing different configurations by hand, it is fairly painless to switch to lightweight DI container such as Needle.

Note 1:
By "configuring your modules", I don’t mean supplying configuration data to your program, but deciding what modules should be selected. For example, while testing you might want to use a mock database layer instead of the real thing.

Creativity in Pairs   27 Oct 04
[ print link all ]
Ralph Johnson writes about creativity being a cooporative effort rather than a solitary endeavor.
Creativity requires breaking out of the ruts of our minds. Working with the right person helps us to be creative because what seems normal to them is strange to us, and our usual way of working seems odd to them. Creative collaboration requires people to differ in some important way.

I certainly experienced that in several different ways over the last few weeks. The first example was my exploration into Ruby-based dependency injection. It was sparked by Jamis Buck’s presentation on Copland at RubyConf2004. I wrote up some ideas and Jamis took those bare ideas and fleshed them out into a real product (Needle) which represents the lightweight view of Dependency Injection. Evind Eklund also made some suggestions for improvements and the final result is something better that any of us would have come up with in isolation.

Another example happened this past month in the weeks after RubyConf. David Heinemeier Hansson has decided to include support for my XML Builder object in Rails. He mentioned to me that support for the processing instructions and declarations would be nice. I threw something together, but got some of the details wrong. Later that evening Rich Kilmer pops up an IM window and together we work out the rough spots in the design. Along the way, we come up with a cool way of supporting namespaces that fits well with the Builder style. It was a great session with each of us contributing and feeding off the ideas of the other.

I think Extreme Programming’s emphasis of pair programming is an attempt to tap into the creative potential of two different minds attacking a problem from different viewpoints. And it works fairly well at that. Now I’m wondering what other activities might benefit from the "two heads are better than one" approach. Maybe I’ll grab a friend and we will think about it together.


Dependency Injection in Ruby   07 Oct 04
[ print link all ]

Introduction

At the 2004 Ruby Conference, Jamis Buck had the unenviable task to explain Dependency Injection to a bunch of Ruby developers. First of all, Dependency Injection (DI) and Inversion of Control (IoC) is hard to explain, the benefits are subtle and the dynamic nature of Ruby make those benefits even more marginal. Furthermore examples using DI/IoC are either too simple (and don’t convey the usefulness) or too complex (and difficult to explain in the space of an article or presentation).

I once attempted to explain DI/IoC to a room of Java programmers (see onestepback.org/articles/dependencyinjection/), so I can’t pass up trying to explain it to Ruby developers.

Thanks goes to Jamis Buck (the author of the Copland DI/IoC framework) who took the time to review this article and provide feedback.

What is Dependency Injection?

Consider the problem of putting together a moderately complex OO program. Typical OO programs create a bunch of objects, wire them together in interesting ways and then let the objects run. It is the first two steps, creating and wiring, that are addressed by Dependency Injection.

By the way, another term for dependency injection is Inversion of Control. Unfortunately, so many things in computer science are called inversion of control that the phrase does not evoke the right connotations with me, so I tend to avoid it. But Inversion of Control is the older term for this pattern so you will see it in many places.

A Moderately Complex Example

One of the problems with explaining Dependency Inversion is that DI only becomes really useful in larger projects. Using a simple example to explain DI leaves the listener thinking "But I can do that easily by (fill in the blank)". So my example is going to be a bit more complex, but hopefully not so large that the reader is unable to understand it.

Imagine you have a webapp that tracks the prices of stocks over time. The application is nicely partitioned into different modules that each handle a portion of the job. A StockQuotes module talks to a remote web service to pull down the current values of the stocks you are tracking. A Database module records the stock values over time. Because this data is highly competitive, you require a login to use the system and thus have an Authentication module to handle validation of user names and password. In addition to these "main" modules, there are a number of additional utility modules used by multiple modules: ErrorHandler to standardize the handling and reporting of error messages and Logger to provide a standard way of logging messsages.

A fully wired system might look something like this:

Building it Old Style!

In the bad, old days, we would just put the logic of building the web app directly into its initialize method. It might look something like this…

  class WebApp
    def initialize
      @quotes = StockQuotes.new
      @authenticator = Authenticator.new
      @database = Database.new
      @logger = Logger.new
      @error_handler = ErrorHandler.new
    end
    # ...
  end

That handles building the WebApp well enough, but what about the subordinate modules. How does the StockQuotes module find out about the logger and error handler, or how does the Authenticator find the database and logger?

We could rewrite WebApp#initialize to create everything in the right order and then pass the logger and error handler to StockQuotes. But that makes the web app rather dependent on details of the StockQuotes module. Currently the database module is created after the quote module, but suppose a change in StockQuotes causes it to need the database. That would require the WebApp to be aware of the change, rearrange the order of creation so that the database is created before the stock quotes module and finally make the database available to the quote service. Yuck!

Even worse, the WebApp knows the concrete name of every module it uses. If I wanted to create an instance of the WebApp for testing, I might want to provide a mock quote service so that I can control the quotes used in testing. Or I might want a mock database for testing. All of these choices are difficult because WebApp knows the class name of all its subordinates.

Enter the Service Locator

We would like to remove the explicit reference to class names in WebApp, but still allow it to locate the services it needs. The Service Locator pattern was designed to address this problem.

With Service Locator, we place references to services in one container and then pass that container to the modules that need to locate those services.

  def create_application
    locator = {}
    locator[:logger] = Logger.new
    locator[:error_handler] = ErrorHandler.new(locator)
    locator[:quotes] = StockQuotes.new(locator)
    locator[:database] = Database.new(locator)
    locator[:authenticator] = Authenticator.new(locator)
    locator[:webapp] = WebApp.new(locator)
  end

The initialize function for a service just uses the locator to find the services. Here is how StockQuotes might look…

  class StockQuotes
    def initialize(locator)
      @error_handler = locator[:error_handler]
      @logger = locator[:logger]
    end
    # ...
  end

Not bad. Now no service is aware of the exact class used for the other services. We can reconfigure the system easily by editted the create_application method.

We use the Service Locator pattern (and variations) at work in our Java system.

External Configuration

Although we built the service locator in Ruby code, it would not be difficult to specify the locator as a configuration file. A simple Ruby method could read the file, instantiate the objects and populate a hash table. This might allow non-programmers to tweak a configuration to their liking.

More Goodness

Another neat thing about the locator is that we can use it to configure data as well as modules. Suppose we wanted to specify the file to be used as the log file. We might modify the create_application method to include the following:

  locator[:log_file_name] = "webapp.log"
  locator[:logger] = Logger.new(locator)

And Logger would have to know that the log file was identified by :log_file_name in the locator. The Database module is another likely candidate for locator based information (e.g. DB user name and password, DB host name).

But …

As good as the Service Locator pattern is, there are still some negatives. Every class that uses the locator needs to be written expecting a locator as an argument to initialize method. This is not a natural idiom for Ruby programmer. In the absence of Service Locators, I would expect that the Logger class would be written like this …

  class Logger
    def initialize(log_filename)
      # ...
    end
    # ...
  end

which would make it unusable in a system that depended upon service locators.

Another downside is that all modules that use the locator must agree on the names of the services. For example, if MyLogger expects its file name to be under :log_filename and YourLogger expects to find its filename under :log_file then the two loggers are not plug replaceable.

Also, Suppose both StackQuotes and Datebase found their loggers using :logger, but we want to give them separate logger instances for some reason. The explicit dependence on the name of the logger service makes this a bit difficult.

And finally, the service locator did not solve the problem of creation order. The database is still created after the stock quotes module, causing problems if the stocks quotes module were modified to use the database.

None of the problems are show stoppers and there are workarounds for each, but it does make us wonder if there is a more general solution.

Finally, Dependency Injection

Dependency Injection is much like using service locators in that we identify the services by name. The big difference is that dependency injectors also take the responsibility of creating the service objects and making sure the dependent services are provided as needed.

This means that the services can be written in complete ignorance of dependency injection framework. All they need to do is make sure that they can be told about the services they need, either through parameters to a constructor, or through some kind of setter.

It also means that dependency injectors are a bit more complicated than service locators, since they also handle the creation of the services as well.

Dependency Injection in Action

How does dependency injection work? Generally, you create a DI container that is configured to know how to create the various services. Then you just ask for a service by name, and the container will create the serice (if needed) and give it to you.

For example, configuring a logger service is as easy as …

   container = DI::Container.new
   container.register(:logger) { Logger.new )

This says that the logger service is named :logger. The first time a logger service is requested, the block supplied to register will be called and a logger object will be created. Subsequent requests for a logger will return the already created logger.

To get a logger service, all you need to do is ask:

   logger = container.logger
Note:In my examples, Service Locators were hash based, so using [] to access the services seems like a natural choice. For dependency injection containers, I chose to use a message-like syntax to access services (e.g. container.logger). Either notation can be used for either service locators or dependency injection containers. In fact, the example dependency injection framework supports both selecter messages and hash-like indexing.

If a logger requires a parameter, then you can easily handle that in the registration block.

  container.register(:logger) { Logger.new("logfile.log") }

If you would rather have the logger get its log filename from the container, you can do this …

  container.register(:logger) { |c| Logger.new(c.log_filename) }

And then somewhere else you can specify the log name …

  container.register(:log_filename) { "logfile.log" }

Configuring the WebApp with Dependency Injection

Now that we’ve seen some DI in action, let’s try it on our web app …

  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

As you can see, it is a bit more complicated than the service locator. The main reason for the complexity is that we have moved the creation logic out of the services and into the DI container. What we have gained is the ability to inject dependencies into any object without having to make special code changes to support it.

Just a few closing notes:

  • Both constructor injection (StockQuotes) and setter injection (ErrorHandler) or a combination of both (WebApp) can be supported with this framework.
  • We can even handle cases where the creation method is not named "new" (DBI).
  • If a poorly written service didn’t provide a way to inject the services it depends upon, we could use instance_variable_set to force a dependent service into place. Obviously, this would be less than desireable.
  • The order of the registration doesn’t matter, since no service is created until everything is registered. If the StockQuotes module suddenly starts needing a database connection, no problem. We just add a reference to a database service in the creation code for StockQuotes and we are done. The DI framework worries about making sure the database is created before anything that needs it.
  • The container doesn’t have to be configured in one place. For example, we could move the first four register calls to a separate file that would allow the log file and database information to be modified independently of the rest.
  • There still needs to be agreement about service names, but now only the container knows about them. The individual services don’t care.
  • Since the DI container is responsible for all the service names and service creation, it is easy to intercept a service and wrap an AOP-like wrapper around a it.
  • Just like the service locator, a DI container could be configured through a configuration file. The configuration would be more complex (because the DI container is more complex), but still quite doable. Another idea is to use Ruby as Domain Specific Language for DI container configuration.

Summary

Both the Service Locator and Dependency Injection patterns are quite useful, but each has different tradeoffs between flexibility and complexity. Understand the differences and you will have all you need to choose the proper idiom for and give circumstance.

You can find the example framework and unit tests here:


 

Formatted: 30-Aug-08 04:43
Feedback: jim@weirichhouse.org