Wednesday, April 29, 2009

Scala Dependency Injection

First in a series... This post flows from my attempts to understand and apply the lessons from the "Modular Programming Using Objects" chapter of the Programming in Scala book by Odersky, Spoon, and Venners. As much as I love that book, I felt that the chapter was a bit too terse for my poor brain, and there's a lot of exploration possible for the ideas found there. In other words, maybe all this is obvious to everybody but me, but here goes...

I'm a constructor injection chauvinist. I don't hate setter injection, but I avoid it if I'm able. I do appreciate that how one does inversion of control is somewhat a matter of taste. But a couple of defenses of my preference come to mind.

First, I like my finals. In Java, member fields assigned in a constructor can be final, and that prevents me from accidentally changing things I shouldn't change. Poka-yoke has saved many a developer many a time. I think it was noted software developer Harry Callahan, who advised us to know our limitations. On second thought, I think he might have been speaking in a different context, but I'm well aware of the kinds of programming mistakes I'm prone to make.

Second, a once-used set method looks like dangling cruft. It's usually public so as to be callable by frameworks, so it lessens the signal to noise ratio of the class's source code. How? Well, the users of the class must be educated not to call the special set method. I'm troubled by a method with a standard name that suggests a particular usage, but then behaves unpredictably if that usage is attempted.

Additionally, the instantiators of the class must be educated to call the special set method, and not to use the class before doing so. It's never a good idea to surprise the coder, and constructors that don't finish constructing will enable partially built objects to exist. Maybe this is just a violation of Poka-yoke again.

In Scala, instead of finals, we have vals. And in addition to dependency injection frameworks like Guice or Spring, we have a lovely way within the language to assemble object graphs. It could well be argued that such frameworks are merely clunky compensators for weaknesses in the Java language itself, such as the lack of mixins.

Imagine an AutoPilot object that needs to ask questions of a FuelSensor object. The fuel sensor has a remaining_liters method that the auto pilot might need to call from time to time. So our object graph comprises an auto pilot object with a pointer to a fuel sensor. This graph has to be instantiated when the program starts.


class FuelSensor {
def remaining_liters: Int = { //blah blah

class AutoPilot(
private[this] val fuel_sensor: FuelSensor) {
// blah blah


A typical Scala approach to dependency injection will encapsulate the initialization of the object graph inside a trait that can be "with"ed into the application.


trait ProductionEnvironment {
val the_fuel_sensor = new FuelSensor()
val the_auto_pilot = new AutoPilot(the_fuel_sensor)
}

object MyApp extends Application
with ProductionEnvironment { // blah blah


Of course, one can initialize Scala object graphs using Spring XML files or Guice annotations, but the trait approach has a nice advantage: if you make a spelling mistake, it's a compilation error, not a runtime problem. Eventually, we're going to see that it enjoys other niceties, too.

In real life, I'll have many environments. For example, when I want to unit test my auto pilot class, I might do something like the following.


trait AutoPilotTestEnvironment {
val the_fuel_sensor = new FuelSensor {
override def remaining_liters: Int = {
// mock implementation here
}
}
val the_auto_pilot = new AutoPilot(the_fuel_Sensor)
}


In the above example, I'm free to use TestNG or ScalaTest if I prefer. Moreover, I can opt for a separate MockFuelSensor class instead of an anonymous one inside the trait. Don't let such details be distracting. The real point is that instead of being in XML-heck with Spring, I can create specific environment traits to assemble meaningful object graphs. And the compiler helps me.

There's a second concrete advantage of the Scala "in-language" approach to dependency injection (DI). I can use Object Oriented (OO) principles -- that is, the separation of the general from the specific -- to organize different configurations thoughtfully.

Suppose for example, that there were two flavors of fuel sensors. Let's emend our code example a bit. A couple of concrete fuel sensor implementations would inherit from the abstract fuel sensor type.


abstract class FuelSensor {
def remaining_liters: Int
// blah blah

class JetFuelSensor extends FuelSensor {
def remaining_liters: Int = { // blah blah

class PropellorFuelSensor extends FuelSensor {
def remaining_liters: Int = { // blah blah


The beauty here is that I can create mixins to mirror the inheritance heirachy of the objects being initialized. Our production environment trait becomes abstract, leaving configuration-specific mixins to handle the varying construction details.


trait ProductionEnvironment {
val the_fuel_sensor: FuelSensor
val the_auto_pilot = new AutoPilot(the_fuel_sensor)
}

trait JetFuelEnvironment {
val the_fuel_sensor = new JetFuelSensor
}

trait PropellorFuelEnvironment {
val the_fuel_sensor = new PropellorFuelSensor
}

object JetApplication extends Application
with JetFuelEnvironment
with ProductionEnvironment { // blah blah


This feels right. Knowledge about how to construct concrete objects can be collocated with their class definitions, if so desired. I can (no pun intended) mix and match my mixin environments to assemble the exact configuration I want for a given application. Spelling errors are detected early (at compile time).

Now for sure, I could do much of this in Spring by including XML fragments inside master configuration files, but I think it's much nicer on the human to use genuine, language-supported OO features. IDE (Interactive Development Environment) support is natural, and that's a big win here.

Stay tuned for more thoughts about Scala dependency injection, and for more refinements of our example. We still have to deal with a handful of "real world" considerations as we transform our toy system into an industrial strength solution. The goal of this post was just to throw up a straw man, whom we can clothe in armor as we go along.

In summary, Scala's support for mixins offers a nice, in-language way to initialize object graphs. Though perfectly compatible with dependency injection frameworks, Scala offers an approach that enjoys a couple of advantages. First, because configurations are code, certain errors are detected early. Second, configuration details can be partitioned meaningfully in traits, and then assembled in a more user-friendly fashion than XML files or annotations.