Update (09 Jul 2015): This post was written for Play 2.3.x. Since version 2.4.0, dependency injection is supported out of the box in Play through Guice. Also, changes in routing at this version of the framework break the approach described here.
Controllers in Play Framework are usually defined as singleton objects. In fact, Play’s documentation defines a controller as a singleton object that generates Action values and provides an example like the one below:
This kind of design is not without problems. Any dependencies of these controllers must be constructed inside the controller, tightening the coupling between them as the controller must now concern itself with one more aspect of its dependencies, namely, their construction.
This increased coupling also make the reuse of these controllers harder as it’s not trivial to tweak them by substituting their dependencies with different implementations. This is particularly relevant when writing unit tests as it’s often very interesting to substitute dependencies for mock objects.
The lifecyle of the dependencies may also become a more present concern: if a dependency is kept as an instance attribute, any mutable state in it will be shared by other requests to this controller, which may be particularly unpleasant in a concurrent environment. Avoiding this requires building the dependency independently at every action where it’s necessary.
A better design
This said, all these problems can be solved by adopting a different design. Instead of using the same singleton object for handling every request, we can have an exclusive controller instance for each request. Instead of having all dependencies statically bound to the controller, we can have them injected in runtime into the controller through its constructor.
Even though Play provides no native support for Dependency Injection, it allows us to take over the job of instantiating and providing the controller objects that will be used to handle the incoming requests. And since we can initialize these objects ourselves, we can also provide them their dependencies while we’re at it. The approach we’ll be using here is outlined in the framework documentation, even though no specifics are given on how to perform the actual initializations and injections.
Instantiating controllers manually
Consider a Play routes
file with the following entry:
GET /action controllers.Application.action
Any request to /action
will be automatically dispatched by Play to the action
method in the Application
singleton object. However, if we prefix the route target with an @
character, the method getControllerInstance
in the Global
object (documentation) will be called to produce an instance of the Application
class.
GET /action @controllers.Application.action
The following snippet contains a trivial implementation for the Global#getControllerInstance
method. It just produces a new instance of the given class. In fact, you don’t actually need to implement it as Global
will inherit the exact same implementation from the GlobalSettings
superclass.
Notice that it takes a class as argument, not a singleton object. And the return of this method will be an instance of the given class. Therefore, our controller cannot be a singleton object as it must be a class instead.
Introducing MacWire
MacWire is an interesting library for performing Dependency Injection in Scala applications. Like an usual DI container, it frees the developer from the tedious wiring of instances and dependencies. Unlike such containers, though, it generates all this wiring code in compile-time, backed by Scala macros. This way, if some class cannot be initialized because, say, some dependency isn’t registered, a compilation error will be raised instead of a runtime error.
The library expects the managed classes to be registered as attributes in a class
, trait
or object
.
Fetching a perfectly wired instance with a static lookup is trivial:
In our case, however, a dynamic lookup will be necessary. MacWire makes this easy too:
Putting it all together
Finally, if we have a Wiring
class such as the one described above, we simply have modify our Global#getControllerInstance
method to use it when building our controller instances:
Notice that we’re building a new instance of Wiring
at every request. This will ensure that the returned controller and its dependencies will all be scoped to a single request. Unnecessary components won’t be constructed as long as they are registered in lazy val
attributes.