Skip to content

NeuronDIAtPlayForScalaTutorial

Christian Schlichtherle edited this page Dec 10, 2018 · 9 revisions

Neuron DI @ Play For Scala - Tutorial

This tutorial explains how to use Neuron DI in a Play for Scala application with the Guice framework. It is assumed that you are already familiar with Guice. If not, please consult the Guice Wiki first. You do not need to be familiar with Neuron DI yet. However, unlike other tutorials, this tutorial is not a comprehensive introduction to Neuron DI, so reading the Neuron DI @ Guice For Scala Tutorial afterwards is recommended. The source code for this tutorial is available on GitHub.

Please read the project setup page first. Make sure your project enables the NeuronDIAtGuiceForScalaPlugin, e.g. like this:

lazy val root = (project in file(".")).enablePlugins(PlayScala, NeuronDIAtGuiceForScalaPlugin)

Note that there is no specific plugin for Neuron DI @ Play For Scala - it's not required. Now let's write a controller which has its dependencies injected by Neuron DI:

package controllers

import global.namespace.neuron.di.scala._
import models.Greeting
import play.api.i18n.Langs
import play.api.libs.json.Json
import play.api.mvc.BaseController
import play.twirl.api.Html
import services.GreetingService

@Neuron
trait GreeterController extends BaseController {

  def greetingService: GreetingService
  def langs: Langs

  lazy val greetingsList: Seq[Greeting] = Seq(
    Greeting(1, greetingService.greetingMessage("en"), "sameer"),
    Greeting(2, greetingService.greetingMessage("it"), "sam")
  )

  def greetings = Action {
    Ok(Json.toJson(greetingsList))
  }

  def greetInMyLanguage = Action {
    Ok(greetingService.greetingMessage(langs.preferred(langs.availables).language))
  }

  def index = Action {
    Ok(Html("<h1>Welcome</h1><p>Your new application is ready.</p>"))
  }
}

At first sight, this doesn't look very different from traditional DI with JSR-330: In a JSR-330 framework like Guice, this would be a class with a constructor with two parameters of the type GreetingService and Langs and an @Inject annotation. Let's go through this in detail:

  • The import global.namespace.neuron.di.scala._ statement makes the Scala API of Neuron DI available.
  • The @Neuron annotation is required for Neuron DI to apply its magic. A class or trait annotated with this annotation is called a neuron class or neuron trait.
  • GreeterController is a trait instead of a class. This is a conscious design decision which generally facilitates reuse. You could also use an abstract class instead.
  • There is no @Inject annotation because Neuron DI has no use for it and hence doesn't even know about it.
  • Dependencies are modeled as abstract, parameterless methods - in this case greetingService: GreetingService and langs: Langs. Such methods are called synapse methods because, in analogy with biology, that's were neuron classes make contact with their dependencies.
  • If you want any of these dependencies to be cached for reuse you could simply change their definition from def to val or declare them as a singleton using the Guice binding DSL (domain specific language).
  • greetingsList is defined as a lazy val instead of val. As with any Scala trait, this is critical or otherwise this value would be initialized before Neuron DI could even initialize its dependency resolution mechanism and hence a NullPointerException would occur. This is a general constraint implied by Scala traits, not Neuron DI.

Last but not least, we need to tell Guice to use the Neuron DI framework for instantiating the GreeterController class and wire its dependencies:

import controllers.GreeterController
import global.namespace.neuron.di.guice.scala._

class Module extends NeuronModule {

  override def configure(): Unit = {
    bindClass[GreeterController].toNeuronClass[GreeterController]
    // alias:
    //bindNeuronClass[GreeterController]
  }
}

Lets go through this in detail again:

  • The import global.namespace.neuron.di.guice.scala._ statement makes the Scala API of Neuron DI @ Guice available.
  • The class is called Module and it's placed in the default package. This is the exact name where the Play framework expects the application to put a custom Guice module, if any.
  • The class extends NeuronModule. Together with the import statement, this is required to make the custom binding DSL available.
  • The single bindClass(...).toNeuronClass(...) statement binds the trait GreeterController to itself as a neuron class. This is required to make Guice aware that GreeterController needs to be instantiated and wired by Neuron DI.
  • Because the interface and implementation classes are the same, the bindClass(...).toNeuronClass(...) statement could be simplified to a single bindNeuronClass(...) call as shown in the comment.
  • GreetingService is a non-abstract class with a default constructor, so Guice will figure a Just-in-time binding for it and hence no binding statement is required in this module.
  • The Play framework provides a built-in binding for the Langs class, so again, no binding statement is required in this module.

That's all it needs to use Neuron DI in a Play for Scala application with the Guice framework. For more details, please read the Neuron DI @ Guice For Scala Tutorial.

Clone this wiki locally