(Ok, I admit I’ve always wanted to write a post with a title like “Design and Evolution of …” or “Structure and Implementation of …”. :-))
In the last two weeks, I’ve been writing (yet) another Dependency Injection framework for Ruby dubbed Dissident. Based on my experience with Needle (which I used for writing Nukumi2) and quite a deal of inspiration by Java frameworks, especially PicoContainer, I think I made one of the most “rubyish” frameworks available.
What is the deal with Dependency Injection? DI tries to solve one of the oldest problems of object-oriented programming: Decoupling objects. Probably every half-serious OO programmer got to know that often you need to pass a fair amount of objects to the classes you instantiate, just because they need them; the class that instantiates doesn’t. All this passing-around is, in the end, unneeded code that wastes time and attracts bugs. Also, what happens if you suddenly need to replace the actual implementations? (Admittedly, it’s not that bad with dynamic languages and duck-typing, but in a static language, I hope you have your interfaces handy. Nevertheless, concentrating instantiation makes your application easier to maintain. Tomorrow your PHB tells you he wants to use that “new logging library” everywhere. It’s really nice if you can do that by changing a single line. [1])
Currently, there are two popular (and generally considered being good) approaches to this problem: Setter Injection and Constructor Injection.
Setter Injection creates new instances and injects the dependencies of them by using setters. Without a DI framework, you would use the class like this in Ruby:
class Application attr_writer :logger def do_stuff logger.log "doing stuff" end end a = Application.new a.logger = Logger.new a.do_stuff
As you can see, this is a straight-forward way to do DI, and also the default one used in my framework. (Well, almost; see below.)
Constructor Injection was popularized by PicoContainer, and is considered more “clean” by many people.
class Application def initialize(logger) @logger = logger end def do_stuff @logger.log "doing stuff" end end a = Application.new(Logger.new) a.do_stuff
Constructor Injection is available for Dissident too, but not the default mechanism because unlike Java and (at least to an extent) Python, Ruby does not provide mechanisms to access the parameter names and types of methods.
Constructor Injection is actually more work to code for in a dynamic language like Ruby, at least in comparison to Setter Injection (count the occurrences of
logger
in above classes).
As mentioned, Dissident does not implement mere Setter Injection, but an extension of it that is only possible in dynamic languages; I call it Method Injection: Dissident extends the classes that use DI to provide methods that return the requested services. Therefore, the Dissident code to make use of the first example would look like this:
class Application
inject :logger
def do_stuff
logger.log "doing stuff"
end
end
class MyContainer < Dissident::Container
provide :logger, Logger
end
Dissident.with MyContainer do
a = Application.new
a.do_stuff
end
You can stop goggling now. :-) You probably expected something like that:
container = MyContainer.new
# mumble mumble
a = container.application
Not so in Dissident! Dissident tries to completely stay out of your
code. Rubyists duck-type class instantiation on #new
, and there is
no reason to change that when using a DI framework.
So, what happens now exactly? The Dissident.with
block makes an
instance of MyContainer
(a plain old Ruby class) the current
container. Now, all “dissidentized” classes (Application
here) can
access it. The inject
line in the class definition of
Application
defines a “getter” for the logger, which is provided by
the container as an instance of Logger
.
In fact, instead of that provide
line, you could as well write
something along this:
class MyContainer < Dissident::Container
def logger
Logger.new
end
end
…which does in above case exactly the same, but provide
allows for
some more subtleties.
The code as seen is not independent of Dissident, but that can be fixed in three easy ways:
Redefine
inject
asattr_writer
(e.g. withclass Class; alias_method :inject, :attr_writer; end
), and you can use standard Setter Injection,rescue calls to
inject
(possibly falling back toattr_writer
), oruse Constructor Injection.
To make use of Constructor Injection in Dissident, just tell provide
the services you want to pass; in above case you now must let
Dissident actually construct the application itself, therefore we need
to register it too:
class MyContainer < Dissident::Container
provide :logger, Logger
provide :application, Application, :logger
end
Dissident.with MyContainer do |container|
a = container.application
a.do_stuff
end
As you can see, Constructor Injection is the more “pure” approach, but a bit more work and not as transparent as Method Injection. You can mix both freely when using Dissident, though.
Another nice thing Dissident provides are parametrized services, simply define a method in your container, and it will get a multiton with a life-time of the container. This only works with Method Injection.
class MyContainer < Dissident::Container
def logger(level=:debug)
SuperFancyLogger.new(:level => level)
end
end
class Application
inject :logger
def do_stuff
logger.log "This is just for debugging."
logger(:alert).log "Core meltdown."
end
end
What happens when you need more than one container? You may, for
example, want to use a library that makes use of Dissident while
independently using it in your own application too. Dissident solves
this by having the classes declare their association with library
:
class AGreatLibrary
library AGreatLibrary
end
class AGreatLibraryHelper
library AGreatLibrary
end
Dissident.with MyContainer, AGreatLibrary => AGreatContainer do
Application.new # uses MyContainer
AGreatLibrary.new # uses AGreatContainer
AGreatLibraryHelper.new # uses AGreatContainer, too
end
If it is likely that AGreatLibrary
always uses AGreatContainer
,
you can declare this too, then the user doesn’t need to care about it
(but still can override manually, of course):
class AGreatLibrary
library AGreatLibrary
default_container AGreatContainer
end
Now, I showed you the most of the things Dissident can do. And these are probably 90% of the things you’ll ever need when using Dissident. Additional features are included as separate files, I wrote a basic lifecycle management and support for multi-methods, that allow even easier parametrization of services; more about that will follow in a later post.
So much about the design of Dissident, now a bit more about the
evolution. I don’t think any library or application I ever wrote
changed so much without a rewrite. When I started to write Dissident,
it was a tiny library that would only do Setter Injection with
instance variables. Then, I noticed this approach was too inflexible
as it didn’t support parametrized services. First, I used
define_method
on the singletons the container instantiated, but
that’s inefficient, and far too invasive. The next step was to
extend
them with Modules, first dynamically generated, then named
for marshaling purposes. I have to admit it took me a fair time to
recognize that I could define the getters directly on the classes.
After some more playing and reading about PicoContainer, I decided to
add Constructor Injection too; that was fortunately rather easy.
But why write a new DI framework at all? There are some prejudices in the Ruby community with respect to that. People say “they make things complicated” and “there are more frameworks than users”. Of course, that may be true—but it shouldn’t be for all of them. Therefore, I decided to make one that’s not complicated, because you barely notice using it (It’s true that use of DI frameworks often significantly changes the code), one that’s easy to pickup, because you can learn it in an afternoon and only need to write a few additional lines of Ruby—no XML or YAML needed, one that actually helps coding, because else it’s a hobble and therefore no fun, one that eases testing, because you can mock the services easily (don’t use a container at all, or simply inject mocks), one that feels like Ruby, because you should never tangle in bad ports of Java libraries; in the end, I decided to make one that I personally like and want to use, because there is no point in making libraries you don’t use on your own.
Still, Dissident is no silver bullet—there is no panacea. If your design is broken, the best libraries can’t change that. But I think that when you use Dissident, and use it as it was meant to be, it can help you show the rough edges of your design earlier than when you sit over half a dozen napkins, desperately trying to untangle your class relationships. (You’ll quickly notice when your container definitions get ugly.)
Another thing among the reasons I wrote my own Dependency Injection framework was the size of the existing ones. Needle with its 1200 LOC doesn’t count as “lightweight” in my opinion anymore—it already is in the mid-size non-invasive team. (It is very good, though. Use it if you think Dissident is not enough or too extreme/weird/fancy/magic/inflexible for you.) Dissident on the other side is one 200 LOC file for the core right now, and maybe 100 LOC for the additional features. The core is unlikely to grow much in future (one thing that probably needs a bunch of code is makeing everything thread-safe), it does basically everything that is needed. Therefore, it does no harm to just include Dissident in your package, that’s one dependency less and you have everything you need.
[1] And that’s the crux with DI, you only know you would have needed it when it’s too late. It’s good if you have a very easy framework then; especially if it’s one that you actually want to use.
NP: Dan Bern—Crossroads