days
1
2
hours
0
3
minutes
3
5
seconds
5
1
How to gain more control with compile-time dependency injection

Compile-time dependency injection in the Play framework

Marius Soutier
Play
Blocks, toy image via Shutterstock

Play introduced Dependency Injection (DI) in version 2.4 to reduce global state, and make it easier to write isolated, reusable, and testable code by instantiating components on each reload and by providing stop hooks. In this article, Marius Soutier explains what compile-time dependency injection in the Play framework is all about.

Motivation

The Play framework used to have a lot of global state, most and for all the current running application that contains things such as the configuration, access to plugins, etc. Another strange thing was the Global object, a singleton used not only to register startup and shutdown hooks but also to override error handlers. All of this leaked into the application lifecycle and the plugin system. For example, you had to define a plugin priority and make sure external components are instantiated lazily and closed after an app restart in development mode. Lastly, global state makes it harder to test your application in a clean way.

Play introduced Dependency Injection (DI) in version 2.4 (Note: this article is up-to-date with Play 2.5) to reduce (Note: global state is not completely gone yet, but will be gradually removed in the next versions) global state, and make it easier to write isolated, reusable, and testable code by instantiating components on each reload and by providing stop hooks.

Runtime DI

The runtime DI mechanism defined by Java Specification Request 330, with its reference implementation Guice, has been selected as the default DI framework in Play. This makes it rather straightforward to start writing new Play applications without worrying too much about DI, as components are injected automatically by using the @Inject annotation. The official documentation on runtime DI is pretty complete so I will not go into detail about it.

Compile-time DI

Guice as a standard mechanism makes sense for a framework that offers a first-class Java API.

As Scala developers, however, we don’t like to rely on reflection, runtime instantiation or to worry about eager bindings. Luckily, Play also provides a compile-time DI mechanism.

In its simplest form, compile-time DI, or any DI for that matter, means passing dependencies to a constructor or a function. All of your dependencies are declared as constructor parameters in your classes, be it controllers, actors, or any other class. When your Play app launches the Application Loader, which replaces the Global object, it prepares all dependencies and instantiates your classes by passing their dependencies into their constructor. The link from controllers to route handling is made by creating a generated router that maps routes to instantiated controllers – again, via their constructors. From that, the Application Loader assembles and provides the current Application.

All parts of Play, now called components, support compile-time DI by providing a trait which ends with Components (Note: a lightweight version of the cake pattern) and builds on three basic dependencies: ApplicationLifecycle, Configuration, Environment.

ApplicationLifecycle allows your component to register a stop hook. Configuration is the TypesafeConfig that you would previously get from a running application. Environment is a collection of methods that revolve around your running Play app, such as the classloader, the current mode (development, test, or production), and obtaining classpath resources. By extending BuiltInComponentsFromContext, you get these three provided by Play.

Let’s say we have a small app with one Application controller that needs to make a webservice call. Now instead of importing Play’s WS which required the current application, we mix in a trait at the application loader level and pass a concrete WS instance to the controller.

package  com.mariussoutier.example

import play.api.{ApplicationLoader, BuiltInComponentsFromContext}
import play.api.libs.ws.ning.NingWSComponents
import play.api.routing.Router
import router.Routes // Routes are generated from the routes file

class AppLoader extends ApplicationLoader {
  override def load(context: Context): Application = new AppComponents(context).application
}

class AppComponents(context: Context) extends BuiltInComponentsFromContext(context) with NingWSComponents {
  // NingWSComponents has a lazy val wsClient
  lazy val applicationController = new controllers.Application(wsClient)
  lazy val assets = new controllers.Assets(httpErrorHandler)

  override def router: Router = new Routes(httpErrorHandler, applicationController)
}

The controller in turn depends on WSClient —which is just a trait.

class Application(ws: WSClient) extends Controller {...}

Finally, we need to tell Play to use our Application Loader in application.conf.

play.application.loader="com.mariussoutier.example.AppLoader"

While we now have more boilerplate to write, this way of building our application has several advantages:

  • Dependencies on components are more explicit

  • We avoid using the current Application

  • We can easily switch to a mocked WS implementation when writing tests

  • When we refer to a new controller in the routes file, the compiler will tell us that Routes is missing a dependency

Testing

Since we control how our application components are assembled, it’s easy to create an ApplicationLoader for our tests.

class FakeApplicationComponents(context: Context) extends BuiltInComponentsFromContext(context) {
  val mockWsClient = ... // You can use Mockito or https://github.com/leanovate/play-mockws
  lazy val applicationController = new controllers.Application(mockWsClient)
  lazy val assets = new controllers.Assets(httpErrorHandler)

  override def router: Router = new Routes(httpErrorHandler, applicationController)
}

ScalaTest supports compile-time DI with its OneAppPerSuite trait.

import org.scalatestplus.play.{OneAppPerSuite, PlaySpec}
class ApplicationTest extends PlaySpec with OneAppPerSuite {
  override implicit lazy val app: api.Application = {
    val appLoader = new FakeAppLoader
    val context = ApplicationLoader.createContext(
      new Environment(new File("."), ApplicationLoader.getClass.getClassLoader, Mode.Test)
    )
    appLoader.load(context)
  }
}

Plugins

Play plugins are now deprecated and replaced by DI component, which means the entire plugin system is gone, along with its configuration file and priorities.

You just provide a component which ideally should be compatible with both runtime and compile-time DI. For this, you typically write a generic API trait and implement it using a Module or a JSR-330 Provider class, and a components trait for compile-time DI.

A basic example to get started is the ActorSystemProvider in Play itself which is also used in compile-time DI via AkkaComponents.

Conclusion

By using compile-time dependency injection we gain more control over how our application is assembled, making it more testable. Writing isolated and testable components is now straightforward and no longer requires an elaborate plugin system. Plus, we don’t have to worry about referring to an application too early.

You can find a full example in my PlayBasics repository under https://github.com/mariussoutier/PlayBasics/tree/master/DependencyInjection.

 


ReactiveTo read more about reactive programming, download the latest issue of JAX Magazine:

Reactive programming means different things to different people and we are not trying to reinvent the wheel or define this concept. Instead, we are allowing our authors to prove how Scala, Lagom, Spark, Akka and Play coexist and work together to create a reactive universe.

If the definition “stream of events” does not satisfy your thirst for knowledge, get ready to find out what reactive programming means to our experts in Scala, Lagom, Spark, Akka and Play. Plus, we talked to Scala creator Martin Odersky about the impending Scala 2.12, the current state of this programming language and the technical innovations that await us.

Thirsty for more? Open the magazine and see what we have prepared for you.

Author

Marius Soutier

Marius Soutier is an independent data engineer. He consults companies on how to best design, build and deploy reactive web applications and realtime big data systems using Scala, Playframework, Kafka, and Spark. You can find him on twitter @mariussoutier or read his blog.


Comments
comments powered by Disqus