Month: December 2015

Playframework 2.4 Dependency Injection (DI)


The Playframework 2.4 has been released after one year of development with the biggest new feature being Dependency Injection (DI). But injecting dependencies, what’s the point? And why does Play require it?

Obviously you were already able to use DI in your Play applications, except for the Play API itself. In previous versions of Play, there is the central Global object which holds global mutable state. Another such object is the currently running Application. Both would cause issues in testing and in development mode when you did not organize and isolate code carefully. This leaked into the application lifecycle and the plugin system, for example you have to define a plugin priority and make sure that external components are instantiated lazily and closed after an app restart (in development mode).

Dependency Injection in Play 2.4 essentially tries1 to get rid of global state and simplifies writing isolated, resuable, 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. 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 a Scala developer however, you might not be too keen on relying on reflection and runtime instantiation. Luckily, Play also provides a compile-time DI mechanism.

In its simplest form, compile-time DI, or any DI for that matter, is just constructor parameter DI. All of your dependencies are declared as constructor parameters in your classes, be it controllers, actors, or any other class. The Application Loader, which replaces the Global object, 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 its constructor.

This requires enabling injected routes in your build.sbt:

routesGenerator := InjectedRoutesGenerator

An Application Loader’s main job is to is provide the current Application.

To do this, you will assemble all of the classes required by your app. All parts of Play, now called components, support compile-time DI by providing a trait that ends with Components and builds on three basic dependencies: ApplicationLifecycle, Configuration, Environment2.

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, dev/test/production mode, and obtaining resources.

The base component trait BuiltInComponentsFromContext extracts environment and configuration from the Application Loader’s context, and instantiates applicationLifecycle.

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

import play.api.{ApplicationLoader, BuiltInComponentsFromContext} import import play.api.routing.Router import router.Routes // Routes are generated from 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) } ▸ Read more...