Quantcast
Viewing all articles
Browse latest Browse all 15

Using Guice with Play! Framework 2.1 for easy Dependency Injection

One of Play! Framework 2.1‘s latest features is the ability to construct controllers, so you can use dependency injection. Briefly, the benefits of using dependency injection is that you will have more modular, extensible, flexible, and testable code. At FortyTwo, we use Guice (a lightweight DI framework by Google) for many of our system components. I’ll show you how to easily integrate Guice into an example Play! 2.1 Scala project and then show you how this greatly improves your codebase’s flexibility.

TL;DR:

At its heart, dependency injection allows you to say something like “I’d like a DbConnection instance”, and not have to worry about where it comes from. Additionally, you can configure exactly which DbConnection to provide elsewhere. So, you can write your component very simply by injecting a DbConnection, and centrally configuring which implementation to use in which conditions. For development and testing, you may want to use an in-memory database, and for production, use a “real” database (example gist).

Integrating Guice with Play! Framework 2.1

For this example, we’ll create and test a very simple translation component. We’ll use a library called sse-guice to simplify Guice syntax, allowing us to use bind[Service].to[ServiceImpl].in[Singleton] instead of bind(classOf[Service]).to(classOf[ServiceImpl]).in(classOf[Singleton]). Ah, much prettier.

First, add Guice and sse-guice to your Build.scala app dependencies:

  val appDependencies = Seq(
    "com.google.inject" % "guice" % "3.0",
    "com.tzavellas" % "sse-guice" % "0.7.1"
  )

We’ll create a simple Translator trait, which will take an input String, and output its translation. For this demo, we’ll implement it with simple word-replacement for French.

trait Translator {
  def translate(input: String): String
}
 
class FrenchTranslatorImpl extends Translator {
  val wordReplacements = Map(
    "hello" -> "bonjour",
    "hi" -> "salut",
    "greetings" -> "salutations"
  )
  def translate(input: String): String = {
    input.split("""\s+""").map(word => wordReplacements.get(word.toLowerCase).map(newWord => word match {
        case _ if word(0).isUpper => newWord.capitalize
        case _ => newWord
      }).getOrElse(word)).mkString(" ")
  }
}

In a real application, you might implement translate() by calling a 3rd party API. Either way, we’ll treat this as something that’s blocking or is particularly slow. For testing, you might not be interested in the actual translation, but how parts of your application work with translated strings. So we’ll make a FakeTranslator (which just returns the input):

class FakeTranslator extends Translator {
  def translate(input: String): String = input
}

Next, we’ll define two modules for different circumstances, DevModule (development and testing) and ProdModule (for production). Whenever we need a translator in production, we’ll use the FrenchTranslatorImpl. Otherwise, we’ll use our more efficient FakeTranslator.

package common.modules
 
import com.tzavellas.sse.guice.ScalaModule
import common.translation._
 
class ProdModule extends ScalaModule {
  def configure() {
    bind[Translator].to[FrenchTranslatorImpl]
  }
}
 
class DevModule extends ScalaModule {
  def configure() {
    bind[Translator].to[FakeTranslator]
  }
}

Let’s create a new controller to use our (very rudimentary) translator.

package controllers
 
import play.api._
import play.api.mvc._
import common.translation.Translator
import com.google.inject._
 
@Singleton
class Translate @Inject()(translator: Translator) extends Controller {
  def greet(name: String) = Action {
    Ok(views.html.greet(translator.translate(s"Hello $name!")))
  }
}

You’ll notice a couple things are different than usual. Normally, Play! controllers are singleton objects, which cannot take parameters (so sadly, can’t be given injected classes). Here, we make Translate a class, and annotate it with @Inject(), which tells Guice to inject parameters. Next, we simply ask for a Translator instance. Notice we don’t ask which translator that we need.

The real magic happens in Global.scala, in a new method to Play 2.1 called getControllerInstance, which returns an instance of a requested controller (with Guice voodoo applied!). Additionally, we create the injector based on which mode Play is running in.

object Global extends GlobalSettings {
  private lazy val injector = {
    Play.isProd match {
      case true => Guice.createInjector(new ProdModule)
      case false => Guice.createInjector(new DevModule)
    }
  }
 
  override def getControllerInstance[A](clazz: Class[A]) = {
    injector.getInstance(clazz)
  }
}

Finally, to let Play! know to use getControllerInstance, we annotate the route with an @:

GET /greet/:name   @controllers.Translate.greet(name: String)

Now, when we start Play! in development mode, we get:

Image may be NSFW.
Clik here to view.

And in production mode:

Image may be NSFW.
Clik here to view.

So what? Why should I use DI with Play?

Notice that we needed no factories and were able to keep our code very nicely organized. We can now easily swap out Translator implementations with no mass code replace. But here’s a really cool benefit: Previously, testing Play! controllers was a lot harder and didn’t let us dynamically inject classes into controllers. We couldn’t easily swap out a HTTP client, email sender, or clock. However, now, we can very easily mock out classes when testing our controllers.

class TranslateSpec extends Specification {
 
  "Translate" should {
    // The normal Play! way
    "accept a name, and return a proper greeting" in {
      running(FakeApplication()) {
        val translated = route(FakeRequest(GET, "/greet/Barney")).get
 
        status(translated) must equalTo(OK)
        contentType(translated) must beSome.which(_ == "text/html")
        contentAsString(translated) must contain ("Barney")     
      }
    }
 
    // Providing a fake Global, to explitly mock out the injector
    object FakeTranslatorGlobal extends play.api.GlobalSettings {
      override def getControllerInstance[A](clazz: Class[A]) = {
        new Translate(new FakeTranslator).asInstanceOf[A]
      }
    }
    "accept a name, and return a proper greeting (explicitly mocking module)" in {
      running(FakeApplication(withGlobal = Some(FakeTranslatorGlobal))) {
        val home = route(FakeRequest(GET, "/greet/Barney")).get
        contentAsString(home) must contain ("Hello Barney")
      }
    }
 
    // Calling the controller directly, without creating a new FakeApplication
    // (This is the fastest)
    "accept a name, and return a proper greeting (controller directly, no FakeApplication!)" in {
      val controller = new Translate(new FakeTranslator)
      val result = controller.greet(name = "Barney")(FakeRequest())
      contentAsString(result) must contain ("Hello Barney")
    }
  }
}

The first way is the typical way Play! controllers are tested. The second is using a custom Global, which explicitly sets the getControllerInstance to return a FakeTranslator instance. Using this pattern, you can easily mock classes that you are testing around. A common use-case is mocking a HTTP client with a class that gives a pre-defined response. Finally, the 3rd method avoids starting a FakeApplication entirely, and instead calls the controller directly with our FakeTranslator.

Conclusion

Play! 2.1 has opened the door for developers to better organize and more thoroughly test their code using dependency injection. Because we can now construct controllers with injected classes, controllers can be more thin and tests are significantly faster. Check out the Github repo for this example, which you can use to bring Guice to your own projects.


Viewing all articles
Browse latest Browse all 15

Trending Articles