Here are a few thoughts and code snippets from my admittedly brief experiments with dependency injection in Scala. Comments most welcome, as always!
Option 0: Use your favourite Java framework
I only know Guice, but I imagine the pros and cons are pretty much the same for any other framework. Porting Java annotations to Scala takes some getting used to, and occasionally it's a bit tricky to get right. And, of course, and it might not quite feel like Scala, but at least you'll know the framework and any quirks it may have.
Plenty of tutorials exist already for this option, so I'm sure your favourite search engine can help you find some. Any links I post here are sure to become stale pretty quickly given the rate at which Scala is developing.
Option 1: Just use implicits
Scala implicit parameters can easily cope with simple cases of DI, and have the added advantage that the code is somewhat more idiomatic Scala than would generally result from Option 0. For example, try pasting the following into an interactive Scala session:
trait Logger {
def log(msg: String)
}
object StdOutLogger extends Logger {
override def log(msg: String) { println(msg) }
}
object Config {
implicit def provideLogger = StdOutLogger
}
import Config._
def logTest(msg: String)(implicit logger: Logger) {
logger.log(msg)
}
logTest("Hello world")
One thing to note is that implicits operate at the function level, whereas DI is normally at the object level (via either constructors or setters). The former can easily be achieved via implicit parameters to an class's constructor, though. Following on from the code above:
class Test(implicit private val logger: Logger) {
def doSomething {
logger.log("Doing something...")
}
}
(new Test).doSomething
Option 2: Adding annotations to implicits
The approach above works fine when you're injecting fairly specific, single-purpose objects. But what if you wanted to inject a string or an integer. Typically DI frameworks use some form of annotation in these circumstances, so let's see if we can come up with a version for Scala implicits (again this should work if pasted into a Scala REPL):
class Annotated[A, T](private val t: T) {
def value = t
}
object DeepBlue {
def answer(implicit annotated: Annotated[DeepBlue.type, Int]) {
println(annotated.value)
}
}
object Config {
implicit def provideAnswer = new Annotated[DeepBlue.type, Int](42)
}
import Config._
DeepBlue.answer
Note that the example above, and the Annotated class in particular, is the simplest possible thing I could come up with that would work, and could likely be improved upon in several ways:
1. I used the (generated) class of the DeepBlue singleton object just because it was handy. Often you'd want to use a class or singleton object created specifically for the purpose. It might even be possible to use case classes or even enumerations to good effect here.
2. It doesn't do anything clever with the annotating type (A). One or more factory methods in a companion object which restrict their arguments a bit more (and maybe the addition of an Annotation trait to help with this) might lead to some nice type inferencing possibilities, especially with singleton objects as in the example. For instance, it would be nice to be able to write the RHS of the provider method as simply Annotated(DeepBlue, 42).
3. I might also be possible to mark the value method as implicit, and have the injection site implicitly convert from an Annotated[A, T] to a T when required. This might be one step too far, though, and just plain confusing rather than helpful (quite a common theme with implicits, unfortunately).
Option 3: For more functionality, implicitly pass an injector
If you need more advanced DI functionality then you may well hit the (somewhat ironic) restriction that all implicit functions (i.e. those that provide the values for implicit parameters) must be explicitly coded, i.e. there's no way (that I know of at least) to programmatically influence the resolution of implicit parameters. I've not spent too much time on this, but the obvious (to me, with my own unique set of influences/biases) way around this would be to build a Guice-like Binding/Module/Injector framework, and then implicitly pass the injector to functions which need injected instances of objects. It may even be possible to use Scala's flexible syntax to create a nice DSL for specifying the bindings. As a warning, though, I have not tried this (yet!) and I suspect it may involve quite a bit of reinventing the wheel, so at this point it may well be worth reverting to Option 0, and using an existing framework.
I hope this was useful, and happy coding!
0 comments:
Post a Comment