dimanche 1 juin 2014

Simple dependency injection with Play, without DI framework

There are many ways to setup dependency injection in a Play Framework project.
In this post, we will see two ways to achieve this as simply as we can, without using a DI framework.

Let’s try to describe our problem :
We have a Play app, with a controller. We provide a route to find Internet links from a query string. To achieve this we will rely on the DuckDuckGO API. But for tests we would like to mock this service. As we want to be able to test our controllers, we need to be able to inject our service into the controller. To add a little more fun, the link service depends on a logging service.

All the code from the following examples can be found on github : http://github.com/loicdescotte/playSimpleDI

The Piece of Cake Pattern

The first solution is to define a ServicesComponent trait. This trait will embed our services :
class LogService{
  import play.api.Logger

  def log(message: String) {
    Logger.info(message)
  }
}

class LinkService(logService: LogService){
  import play.api.libs.ws.WS

  def findLinks(query: String) = {
    val duckDuckUrl = Play.current.configuration.getString("duckduck.url").getOrElse("http://api.duckduckgo.com/?format=json&q=") + query
    WS.url(duckDuckUrl).get.map{ response =>
      val results = response.json \\ "FirstURL"
      //take first result if exists
      val result = results.mkString(", ")
      logService.log("found links : " + result)
      result
    }
  }
}

trait ServicesComponent{
  val logService = new LogService
  val linkService = new LinkService(logService)
}
Now the controller can extend this trait to get dependencies from it :
trait ApplicationController extends Controller {
  this : ServicesComponent =>

  def findLinks(query: String) = Action.async {
    val links = linkService.findLinks(query)
    links.map(response => Ok(views.html.index(response)))
  }

}

object Application extends ApplicationController with ServicesComponent
this : ServicesComponent => in the ApplicationController trait means that we need a ServicesComponent dependency.
Play use objects to wire routes to controllers. At the end of the sample code above, we create the Application instance that will embed the services by extension of the ServicesComponent trait.
This is a very light version of the cake pattern. I use only one layer and traits only where they are needed. Everything is wired in the ServicesComponenttrait.
If we want to wire another version of a service, for example for tests, we just need to extend the ServicesComponent trait and override the value :
class ControllerSpec extends Specification with Mockito {

  trait MockServices extends ServicesComponent {
    override val logService = mock[LogService]
    override val linkService = {
      val mockLinkService = mock[LinkService]
      mockLinkService.findLinks("hello") returns Future("http://coucou.com")
      mockLinkService
    }
  }

  "Application" should {

    "display query results" in {

      object Application extends ApplicationController with MockServices
      val response = Application.findLinks("hello")(FakeRequest())
      status(response) must equalTo(OK)
      contentType(response) must beSome("text/html")
      contentAsString(response) must contain("http://coucou.com")

    }
  }
}
That’s all!

The ‘@’ way

Alternatively, we can use a method provided in the Play’s GlobalSettings trait : getControllerInstance[A]. The goal of this method is to allow controllers instantiation at runtime.
To make this work we will create an “instance map” with an instance of controller for each class of controllers.
This map can be seen as a very light DI container :
object Global extends GlobalSettings {

  val services = new ServicesComponent
  val instanceMaps: Map[Class[_], AnyRef] = Map(classOf[Application] -> new Application(services))
  override def getControllerInstance[A](controllerClass: Class[A]) = instanceMaps(controllerClass).asInstanceOf[A]

}
To tell Play to use getControllerInstance, we use the @ char in the route definition :
GET     /                           @controllers.Application.findLinks(query: String)
To make this work, we just need a few changes from the first example : ServicesComponent will be a class and not a trait, so we can instantiate it directly.
class ServicesComponent{
  val logService = new LogService
  val linkService = new LinkService(logService)
}
Now, our controller looks like this :
class Application(services: ServicesComponent) extends Controller {
  def findLinks(query: String) = Action.async {
    val links = services.linkService.findLinks(query)
    links.map(response => Ok(views.html.index(response)))
  }
}
Note : we get the dependencies by composition, while in the first example we got it by inheritance with traits.
Finally, we can create our mock and test the controller :
class ControllerSpec extends Specification with Mockito {

  val mockServices = new ServicesComponent {
    override val logService = mock[LogService]
    override val linkService = {
      val mockLinkService = mock[LinkService]
      mockLinkService.findLinks("hello") returns Future("http://coucou.com")
      mockLinkService
    }
  }

  "Application" should {

    "display query results" in {

      val appController = new Application(mockServices)
      val response = appController.findLinks("hello")(FakeRequest())

      status(response) must equalTo(OK)
      contentType(response) must beSome("text/html")
      contentAsString(response) must contain("http://coucou.com")

    }
  }
}

Conclusions

Both methods help using dependency injection and ease testing, without need of any DI framework.
The first approach has an advantage : everything is wired at compile time, thanks to Scala DI features. On the other hand, the ‘@’ method can be used both in Java and Scala.