Idea Transcript
subtype
Constructor injection in Scala Posted on October 6, 2011
There are many ways to do dependency injection (DI) in Scala. Traditional Java DI frameworks such as Guice, PicoContainer and Spring Framework work just fine with Scala. There are even Scala specific DI libraries, such as subcut and sindi, which offer nicer syntax and additional type safety. For me the best part is that you don’t necessarily need any DI framework in Scala. The language is powerful enough to abstract injection quite nicely. The most famous DI technique in Scala is the Cake Pattern. The Cake Pattern is wonderful but bit heavy and in fact it offers more than just DI. The Cake Pattern article also describes two other common methods: using implicit values and structural typing. What I describe next is a version of implicit injection.
Implicit constructor injection in Scala Scala allows to perform implicit constructor injection in quite elegant manner. This is should not be nothing new, of course, but having a neat summary of this pattern is certainly helpful. The terminology used in examples below is borrowed from Guice (modules, binding and injector). It is also worth pointing out the Implicit Environment Pattern which has some similarities. Without further ado, the recipe:
// Service interfaces (optional) trait PlumbingService trait PorcelainService // Implementations declaring dependencies using implicit keyword class PlumbingServiceImpl extends PlumbingService class PorcelainServiceImpl( implicit plumbingSrv: PlumbingService) extends PorcelainService // Dependency modules declaring bindings trait RoyalModule { lazy implicit val plumbingService = new PlumbingServiceImpl lazy implicit val porcelainService = new PorcelainServiceImpl } // Compose an injector from modules class RoyalInjector extends RoyalModule // Profit new RoyalInjector().porcelainService
Summary: 1. Declare service dependencies using implicit keyword in constructor 2. Create dependency modules using traits 3. In each module use ‘implicit lazy val’ to bind dependencies 4. Compose injector class from modules
On usage Overriding dependencies is easy when needed:
trait RoyalTestModule extends RoyalModule { override lazy implicit val plumbingService = new MockPlumbingService }
Addition safety measures can be added for extra robustness:
class PorcelainServiceImpl( implicit plumbingSrv: PlumbingService) extends PorcelainService { require(plumbingSrv != null) // fail-fast if null } trait RoyalModule { // use protected keyword and specify type explicitly protected lazy implicit val plumbingService: PlumbingService = new PlumbingServiceImpl }
“abstract implicit lazy val” would allow abstract binding but unfortunately this doesn’t compile. Luckily, adding one level of indirection helps:
trait MyModule { implicit lazy val myService = createMyService() def createMyService() // abstract method }
Ambiguity problem when injecting multiple different values with the same type signature can be solved either by assigning ambiguous values manually or by using annotated types:
class Annotated[A, T](val value: T) class MyService(implicit hostname: Annotated[MyService, String]) trait MyModule { lazy implicit val myServiceHost = new Annotated[MyService, String]("localhost") }
Finally, I’d like to mention “factory method” injection which can be useful in certain cases where new instance is preferred to singleton instance. As a side note, this is a good example where Cake Pattern performs better since it allows to use def in very natural way.
trait MyModule { lazy implicit val fooFactory: () => Foo = () => new FooImpl }
However, using explicit type such as FooFactory might be more obvious than () => Foo.
The Good, the Bad and the Ugly The Good: Simple Immutable Compile-time injection and type-checking Clean, only implicit keyword is used to declare a dependencies Easy to override when necessary, say, for testing purposes. No need to poison application code with implicit imports (e.g. import FooServices._) Not a library (zero dependencies) The Bad: Cyclic lazy vals will result in a stack overflow on first access (e.g. A depends on B and B depends on A). Breaks super class contract when overriding dependencies: overriding non-abstract values is some what treacherous since val should be immutable forever. One possible workaround is to use “abstract implicit lazy val” at the price of one extra line. Not a library (would offer usage guidance and likely to prevent some mistakes). The Ugly Slightly verbose code in modules Ambiguous implicits must be assigned either manually or some sort of extra type annotation is needed. Finally, the warmer example using the pattern:
// service interfaces trait OnOffDevice { def on: Unit def off: Unit } trait SensorDevice { def isCoffeePresent: Boolean } // service implementations class Heater extends OnOffDevice { def on = println("heater.on") def off = println("heater.off") } class PotSensor extends SensorDevice { def isCoffeePresent = true } // service declaring two dependencies that it wants injected class Warmer( implicit val sensor: SensorDevice, implicit val onOff: OnOffDevice) { def trigger = { if (sensor.isCoffeePresent) onOff.on else onOff.off } } // module binding dependencies trait ServicesModule { protected implicit lazy val potSensor: SensorDevice = new PotSensor protected implicit lazy val heater: OnOffDevice = new Heater } // compose an injector from modules class AppInjector extends ServicesModule { implicit lazy val warmer = new Warmer } // use injector. the wiring is done automatically using // the implicits new AppInjector().warmer.trigger
To highlight main differences to original warmer example pattern: Dependency binding is done in traits instead of objects. Application code is fully unaware of dependency management where as original had to introduce management logic (import Services._). Injection logic is centralized to injector class in contrary to scattered code fragments (import Services._) in application code. This is it for now. Happy hacking!
Report this ad
Report this ad
Share this:
Twitter
Facebook
Like Be the first to like this.
This entry was posted in scala. Bookmark the permalink.
One Response to Constructor injection in Scala Adam Warski says: October 10, 2011 at 2:51 pm
Nice Although one disadvantage, same as with the cake pattern, is that the configuration is fixed, that is you can dynamically decide which implementation of a dependency you want. Reply
subtype Blog at WordPress.com.