Play can be considered to be a flexible framework that doesn’t force the users to follow a certain path prepared by its designers. You have a lot of options to choose from when it comes to a dependency injection mechanism. The default solution proposed by Play is JSR 330 with Guice implementation. In fact Play is DI‑agnostic – you can use various forms of compile time dependency injection, including the cake pattern and functional techniques, such as the reader monad. It may even sometimes be reasonable to mix different techniques. I’ll try to shortly describe some of the options that you have. Since some of the concepts described here are not really Play‑specific, they can be used in every Scala application.
Jump to:
The example application
The sections below contain descriptions of dependency injection mechanisms used in Scala Play applications. They all provide the same sample functionality.
The sample application exposes three HTTP endpoints: one that returns a list of books, one that returns a single book by its ID, and one that allows to update a book’s title. HTTP requests are handled by the BooksController that depends on the BooksService , which provides the data. Implementation of the BooksService – CachingBooksService depends on the CacheApi component that is provided by Play. So, we’re going to see how to declare dependencies between components, define our own “injectable” component (BooksService) and access a component provided by Play out of the box (CacheApi).
The application is very simple, just to provide examples of DI usage, it has no HTML views. Play 2.5.4 and Scala 2.11 versions are used in the examples.
Here is the code that doesn’t use any DI yet, the dependencies are just declared as constructor parameters.
case class Book(id: Int, title: String) object Book { implicit val jsonFormat = Json.format[Book] } trait BooksService { def list: Seq[Book] def get(id: Int): Option[Book] def save(book: Book): Unit } class CachingBooksService(cache: CacheApi) extends BooksService { private val db = mutable.Map( 1 -> Book(1, "Twilight"), 2 -> Book(2, "50 Shades of Grey")) //simulates some persistent storage override def list: Seq[Book] = { //get "books" entry from cache, if it doesn't exist fetch fresh list from the "DB" cache.getOrElse("books") { def freshBooks = fetchFreshBooks() cache.set("books", freshBooks, 2.minutes) //cache freshly fetched books for 2 minutes freshBooks } } override def get(id: Int): Option[Book] = { cache.getOrElse(s"book$id") { def freshBook = fetchFreshBook(id) cache.set(s"book$id", freshBook, 2.minutes) freshBook } } override def save(book: Book): Unit = { db(book.id) = book } private def fetchFreshBooks(): Seq[Book] = { db.values.toSeq.sortBy(_.id) } private def fetchFreshBook(id: Int): Option[Book] = { db.get(id) } } class BooksController(booksService: BooksService) extends Controller { def get(id: Int) = Action { booksService.get(id).fold(NotFound: Result) { book => Ok(Json.toJson(book)) } } def list = Action { def books = booksService.list Ok(Json.toJson(books)) } def updateTitle(id: Int) = Action(parse.text) { request => booksService.get(id).fold(NotFound: Result) { book => val updatedBook = book.copy(title = request.body) booksService.save(updatedBook) NoContent } } }
routes file looks as follows:
GET /books/:id controllers.BooksController.get(id: Int) GET /books controllers.BooksController.list POST /books/:id/updateTitle controllers.BooksController.updateTitle(id: Int)
The default: Guice
JSR 330 approach is provided out of the box by Play, with default implementation being Guice library. Being both the default approach and runtime dependency injection, this technique requires the least effort for the programmer to configure. All components provided by the framework are ready to be injected. If you use the default router, you don’t have to bother with instantiating it, as the router has controllers automatically injected. You also don’t have to create custom application loader for this mechanism to work.
Open position for Scala developer
Defining components
You don’t have to explicitly declare that a given class is a component, so to create an injectable component simply create a class. If you want to (most likely you do) create components that implement an interface and make that interface injectable, you have to either use @ImplementedBy annotation or define a module class.
import com.google.inject.ImplementedBy @ImplementedBy(classOf[ConcreteBooksService]) trait BooksService { // ... } class ConcreteBooksService extends BooksService { // ... }
The approach taken in the above example requires the least effort but the existence of implementation‑class (ConcreteBooksService) has to be known by the BookService. Sometimes, the BookService may come from an external library and you can’t annotate it. I’d say that it’s not a good practice to use @ImplementedBy at all since it’s only supposed to define a default dependency, that can be overridden in the binding code.[1] Instead of @ImplementedBy you can define a Guice module class:
import com.google.inject.AbstractModule import play.api.{Configuration, Environment} class GuiceModule(environment: Environment, configuration: Configuration) extends AbstractModule { override def configure() = { bind(classOf[BooksService]).to(classOf[ConcreteBooksService]) } }
Here, you bind ConcreteBooksService as an implementation of a BooksService. As you can see, the GuiceModule class has access to an environment and a configuration , so you can bind different implementations depending on them.a To use this module, you have to enable it in your application.conf using play.modules.enabled configuration property:
play.modules.enabled += "modules.GuiceModule"
Depending on components
Using JSR 330 means using annotations from javax.inject package. To declare a dependency on some component you can annotate class’ constructor with @Inject:
class BooksController @Inject()(booksService: BooksService) extends Controller { // ... }
Constructor injection is not the only way to inject a dependency but setter-injection is a bad practice[2] [3] , so I’ll just skip it.
As said before, APIs provided by Play are ready to be injected, so you can depend on them using @Inject without any additional configuration:
class CachingBooksService @Inject()(cache: CacheApi) extends BooksService { // ... }
Summary
Using Guice has some advantages: it’s easy to configure, flexible, needs minimal boilerplate code and is supported out of the box by the framework. The main disadvantage is that it’s a runtime DI mechanism, what means that the compiler won’t detect component wiring problems. In some situations, you may get an error not only on application initialization but also later in the process of executing the logic.
For details about Guice itself, components scope and many other important topics see its excellent documentation.
The example application is implemented in the guice module of the example project; see Running the examples chapter for more information about example code.
Do it yourself: The Manual DI
Dependency injection is about inverting the control – your components aren’t responsible for constructing dependencies, they have them injected instead. You may think it’s simpler to implement that because you don’t need any special constructs or libraries to achieve this. If your component classes declare their dependencies in constructor parameter list, you can create a module class and construct all the components manually:
class Module { val cache: CacheApi = new // ... val booksService: BooksService = new CachingBookService(cache) val booksController: BooksController = new BooksController(booksService) }
As you can see, the Module class, once instantiated, has access to all components, and these components have their dependencies injected. In the application code, you access all the components using a Module instance. The DI module, (aka an assembler) itself is not anything specific to the manual DI technique,[4] it’s a concept that every DI mechanism uses but some frameworks may hide its existence from the developer.
There is no special way to declare a dependency or a component, any class that provides a public constructor or a factory method can be made a component by instantiating it in the module. All Play components fulfil these criteria.
Manual DI in Play
To use a different DI technique than Guice, you need to create a custom ApplicationLoader implementation. You have to wire all the components needed both for the Play itself and for your application code. Play helps you to achieve this by providing a BuiltInComponentsFromContext abstract class.
import controllers.BooksController import play.api.BuiltInComponents import play.api.cache.EhCacheComponents import services.{BooksService, CachingBooksService} import router.Routes class ApplicationLoader extends play.api.ApplicationLoader { def load(context: Context) = new ApplicationModule(context).application } class ApplicationModule(context: Context) extends BuiltInComponentsFromContext(context) with EhCacheComponents { lazy val booksService: BooksService = new CachingBooksService(defaultCacheApi) lazy val booksController = new BooksController(booksService) lazy val router = new Routes(httpErrorHandler, booksController) }
In the above example, the ApplicationModule class defines a DI module. It extends BuiltInComponentsFromContext class that provides components needed by the framework itself, such as Configuration and HttpErrorHandler instances, and EhCacheComponents trait that provides a cache implementation needed for BookService. Later, BooksService and BooksController components are constructed. The router implementation cannot be provided by Play automatically in BuiltInComponentsFromContext because it’s generated from routes text file into the router.Routes class – you have to construct it manually. As you can see, all dependencies must be both constructed and wired by yourself.
You may have noticed the usage of lazy vals instead of regular vals. If you use simple val definitions, you may encounter NullPointerExceptions if you forward-reference another field. Lazy definitions ensure correct initialization order, so you don’t have to worry about it but they come with a price – access to them must be synchronised.
To use the module, a custom application loader must be defined (ApplicationLoader). To override Play’s default application loader, provide play.application.loader configuration property in application.conf and set it to fully qualified class name of the loader.
Summary
The manual DI’s advantage is mainly compile‑time wiring correctness verification. You can feel a bit safer when running your applications on top of it, compared to the Guice solution. Second good thing is that you don’t depend on any DI library. The bad thing is that you are required to construct and wire everything manually which can be a pain with a large number of components. For simple applications, Guice may be an overkill though, so manual DI is always worth considering.
The example application is implemented in the manual module of the example project, see Running the examples chapter for more information about example code.
MacWire library – macros!
MacWire is a library that provides Scala macros that make manual DI much less painful. The concept is very similar to the manual DI but MacWire’s macros handle the wiring and check it, all at the compile time obviously. The library provides more than just macros, but in this article, I’ll focus on the core functionality that is macro-only.
Simplifying the manual DI
Remember the module definition from the chapter about the manual DI? Let’s rewrite it using MacWire:
class Module { import com.softwaremill.macwire._ val cache: CacheApi = // ... val booksService: BooksService = wire[CachingBookService] val booksController: BooksController = wire[BooksController] }
As you can see, constructor invocations were replaced with wire macro invocations. The macro resolves dependencies and expands to the code very similar to, or even the same, as the one from the manual DI chapter. If a dependency cannot be found, a compilation will fail with a descriptive message.
Using MacWire in Play
Because MacWire in its core functionality simplifies the manual DI, its usage in Play is very similar to the manual DI. You have to implement a custom application loader that will instantiate a DI module:
import controllers.BooksController import play.api.BuiltInComponents import play.api.cache.EhCacheComponents import services.{BooksService, CachingBooksService} import router.Routes class ApplicationLoader extends play.api.ApplicationLoader { def load(context: Context) = new ApplicationModule(context).application } class ApplicationModule(context: Context) extends BuiltInComponentsFromContext(context) with EhCacheComponents { import com.softwaremill.macwire._ lazy val cache: CacheApi = defaultCacheApi lazy val booksService: BooksService = wire[CachingBooksService] lazy val booksController = wire[BooksController] lazy val router = wire[Routes] }
One interesting thing in the above example is that the cache field had to be declared in the ApplicationModule class, even though defaultCacheApi is already provided by EhCacheComponents. This had to be done because EhCacheComponents defines two members that have CacheApi value – the wire macro can’t decide by itself what value should be used when instantiating CachingBooksService. By defining a field cache we tell wire to use the defaultCacheApi.
Summary
MacWire is a nice library that has all the benefits of the manual DI but it helps a lot with the wiring. If you want a compile-time DI and you are not scared by macros messing with the code you wrote, then it’s a solid choice. Check out more about MacWire at its GitHub page.
The example application is implemented in macwire module of the example project, see Running the examples chapter for more information about example code.
The cake pattern
The cake pattern is a popular compile-time dependency injection mechanism that uses Scala’s language features only to wire components. In this pattern, for each component you create, you need to implement a trait describing it. A DI module (aka cake) is created by “stacking” component traits, i. e. module class mixes in all the component traits. Play framework supports it by providing ready-to-use component traits. In contrast to the Guice solution, this DI technique requires much more boilerplate code and explicit configuration.
Defining components
The cake pattern comes in a couple of flavors. In the simplest form, a BookService component with a dependency on a CacheApi can be defined as follows:
trait BookServiceComponent { def cache: CacheApi //a dependency lazy val bookService = new BookService(cacheApi) } class BookService(cache: cacheApi) { // ... }
Another option is to use self-type annotation:
trait CacheComponent { lazy val cache: CacheApi = // ... }
trait BookServiceComponent { this: CacheComponent => //a dependency lazy val bookService = new BookService(cacheApi) } class BookService(cache: cacheApi) { // ... }
In the above example we require that whenever a BookServiceComponent trait is mixed in, the class which mixes it in has to implement a CacheComponent trait. You can declare more dependencies by extending a self-type reference:
trait BookServiceComponent { this: CacheComponent with OtherComponent => lazy val bookService = new BookService(cacheApi, otherComponent) }
A module definition would look as follows:
class Module extends CacheComponent with BookServiceComponent
Then, when you instantiate a Module class, all component instances inside the module will have their dependencies injected – it’ll just work by using language rules. You can obtain instances of all the components through the Module instance.
Separating interfaces from implementations
In the real world, you normally would define an interface trait and implementations for your components. To achieve this with self-type cake pattern flavor you would do something similar to this:
trait BookService { // ... } trait BookServiceComponent { def bookService: BookService } class CachingBookService(cache: cacheApi) extends BookService { // ... } trait CachingBookServiceComponent extends BookServiceComponent { this: CacheComponent => lazy val bookService = new CachingBookService(cache) }
If you use approach used in the above snippet, you can create components that depend on other components without the need of knowing anything about their concrete implementations:
trait BooksControllerComponent { this: BookServiceComponent => //dependency on any implementation of BookServiceComponent // ... }
Using the cake pattern in Play
The same as with the manual DI, you need to create a custom ApplicationLoader implementation. Again, BuiltInComponentsFromContext class helps you to provide core Play components to your cake.
import controllers.BooksControllerComponent import play.api.ApplicationLoader.Context import play.api.cache.EhCacheComponents import router.Routes import services.CachingBooksServiceComponent class ApplicationLoader extends play.api.ApplicationLoader { def load(context: Context) = new ApplicationModule(context).application } class ApplicationModule(context: Context) extends BuiltInComponentsFromContext(context) with BooksControllerComponent with CachingBooksServiceComponent with EhCacheComponents { lazy val router = new Routes(httpErrorHandler, booksController) }
In the above example, ApplicationLoader class is defined what instantiates a ApplicationModule class serving as a cake. BuiltInComponentsFromContext provides default implementations of internal Play components, such as Application and Configuration instances. CachingBooksServiceComponent requires a cache implementation, so Play’s EhCacheComponents trait is also mixed into the module. If you use the default router, the Routes class is generated on compile‑time from a routes file, so it cannot be made ready in BuiltInComponentsFromContext – you have to instantiate it yourself.
Summary
Similarly to the manual DI, the cake pattern’s advantage is mainly compile‑time verification of the wiring, and no dependencies on external libraries. Component construction is manual but, contrary to the manual DI, wiring is automated by the Scala compiler. The bad thing is that this pattern requires you to write even more boilerplate code than the manual DI.
The example application is implemented in the cake module of the example project, see Running the examples chapter for more information about example code.
The reader monad
“Wait, what is this thing?”
I intentionally left the reader monad as the last technique described to let you enjoy a nice long‑lasting aftertaste after reading the article. The reader monad is basically a monad for unary functions, that uses Function1.andThen method as a monadic map operation. If you don’t know what a monad is, don’t worry. If you use Scala or another functional language, you probably use it all the time anyway.
To understand what the reader is, first consider the following functions:
val square = (x: Int) => x * x val divBy2 = (x: Int) => x / 2.0
square function is Int => Int, and divBy2 is Int => Double. You can chain them, what will create a new Int => Double function:
val chained = square.andThen(divBy2) chained(3) //equal to divBy2(square(3))
The reader will turn your unary functions to monads, meaning that you can use map and flatMap methods on them. In turn that enables you to chain invocations in Scala’s for comprehensions and do all the monady goodness. You can pretty easily craft your own reader implementation but you can also use one from the available libraries. I’ll use scalaz library in the example.
import scalaz.Reader val square = Reader((x: Int) => x * x) val divBy2 = Reader((x: Int) => x / 2.0) val chained = square.map(divBy2) chained(3) // equivalent to divBy2(square(4))
The example above is analogous to the previous one but it uses readers instead of simple functions. Now look at this snippet:
import scalaz._ val squareAndDivBy2Sum = for { s <- square d <- divBy2 } yield s + d sumSquareAndDivBy2(4) // equivalent to square(4) + divBy2(4)
The above example creates a reader that is equivalent to function
(x: Int) => square(x) + divBy2(x)
by using a for comprehension. The for comprehension is just a syntactic sugar of
square flatMap (s => divBy2 map (d => s + d))
which may be more or less readable depending on a personal taste or one’s functional programming constructs familiarity level.
Unleash The Power of the Reader to do some DI
There are a couple of ways to declare a dependency. You can declare a dependency as a class’ constructor parameter, meaning that the whole class requires this dependency to work:
class BooksService(cache: CacheApi) { // ... }
I took this approach in the previous chapters. Another possibility is to declare a dependency as a method parameter, and then, only methods that really require the dependency have it declared:
class BooksService { def get(id: Int)(cache: CacheApi) : Book = { //this method has a dependency on the cache // ... } def doOtherThing(book: Book) { //this method doesn't have a dependency on the cache // ... } }
This is good because when there is a client that only wants to call doOtherThing it doesn’t have to bother with the CacheApi at all. Second good thing is that methods don’t have access to instances they don’t need access to. The downside is that this approach clutters method’s parameter lists. Marking method’s parameter list containing dependencies as implicit can alleviate this cluttering problem but not solve it completely.
Both constructor injection and parameter injection require that when the method requiring a dependency is called by the client, the dependency is already instantiated and ready to use. This is different when using the reader monad. How to use the reader for the dependency injection? Let’s look at the following example:
class BooksService { def get(id: Int) = Reader[CacheApi, Book] { cache => cache.getOrElse(id) { def freshBook = fetchFreshBook(id) cache.set(s"book$id", freshBook, 2.minutes) freshBook } } }
As you can see, the get method doesn’t declare a dependency as one of its parameters, it declares it in a return type. It doesn’t return a Book, it returns the reader that accepts the dependency (CacheApi) and returns a Book. So when a client code calls the service with service.get(1) it doesn’t get the Book instance right away – it gets the reader that can return the value when you provide it the required dependency – in this case, an instance of CacheApi.
val bookReader = service.get(4) //get the reader val book = bookReader.run(cache) //read the value by providing the dependency
The above snippet can be shortened to:
val book = service.get(4)(cache)
The advantage is that you can combine the reader that is returned by service.get(4) with other readers, do some transformations, and finally provide all the dependencies to get the result.
What your mom didn’t tell you
Some people argue that the reader monad can replace the functionality of other dependency injection mechanisms. I say that’s not entirely true. While the reader monad is a great functional tool, in my opinion, if used as a DI technique, it performs best in conjunction with object-oriented DI mechanisms. The problem is that when you declare dependencies in method signatures, as it is done with the reader monad, you can’t separate dependencies of concrete component implementations from their interfaces. Take a look at the following example:
trait BooksService { def get(id: Int) : Book } class CachingBooksService extends BooksService { override def get(id: Int) = Reader[CacheApi, Book] { // ... } }
Obviously, this won’t compile because CachingBooksService.get method’s return type is not compatible with BooksService.get method’s return type. Normally, you’d want to hide the dependency on CacheApi from BooksService clients, which is impossible to do with the reader monad approach. My take on this is to use other object-oriented DI mechanism, and complement it with the reader monad where possible.
Let’s see how Guice works with the reader monad:
object Book { implicit val jsonFormat = Json.format[Book] def get(id: Int) = Reader[BooksService, Option[Book]] { service => service.get(id) } def list() = Reader[BooksService, Seq[Book]] { service => service.list } } case class Book(id: Int, title: String) { def save() = Reader[BooksService, Unit] { service => service.save(this) } } trait BooksService { def list: Seq[Book] def get(id: Int): Option[Book] def save(book: Book): Unit } class CachingBooksService @Inject() (cache: CacheApi) extends BooksService { // ... } class BooksController @Inject()(booksService: BooksService) extends Controller { def get(id: Int) = Action { Book.get(id).map { case None => NotFound case Some(book) => Ok(Json.toJson(book)) }.run(booksService) } def list = Action { Book.list().map { books => Ok(Json.toJson(books)) }.run(booksService) } def updateTitle(id: Int) = Action(parse.text) { request => Book.get(id).flatMap { case Some(book) => book.copy(title = request.body).save().map(_ => NoContent) case None => Reader[BooksService, Result](_ => NotFound) }.run(booksService) } }
The great thing about the reader monad is that it can be used in components that are not managed in any way by any DI framework, so it can be for example used in the domain layer, avoiding so-called anemic domain model.[5] [6] In the above example, it’s been used in Book case class and its companion object. The BooksController has the service injected by Guice. Pay special attention to the updateTitle method. It uses save and get methods without providing the service right away which looks pretty clean because you don’t have to clutter your logic by providing the dependencies. This is a big advantage in the real-world scenario, when you have to work with many dependencies, results from many components, chain invocations, and transform results. The value is demanded from the reader by invoking run or apply method that provides a dependency. Normally, the logic of updateTitle probably shouldn’t be defined in the controller but you get the idea.
Summary
The reader monad is a special DI mechanism in the sense that it’s not object oriented technique but it is functional. As a DI technique, it fits well in Scala, a language that combines object-oriented and functional worlds, allowing you to use the reader monad in conjunction with other dependency injection styles.
The example application is implemented in the reader module of the example project, see Running the examples chapter for more information about example code.
Conclusion
I hope that this article managed to provide an overview of some dependency injection techniques and how to use them in applications based on the Play framework. There certainly is no silver bullet when it comes to choosing a DI mechanism. My personal favourite is MacWire combined with the reader monad. I consider the cake pattern too bloated and I reject Guice because it works at runtime. It’s good to have freedom of choice, and this is what the Play framework gives you.
Running the examples
The source code of all example applications is available on GitHub here. The easiest way to run the applications is to clone the git repository and use sbt. If you work with Play, you probably already have sbt installed. If not then follow the instructions on the sbt website.
$ git clone https://github.com/povder/play-di.git $ cd play-di $ sbt
After entering sbt interactive mode, choose desired sample project (guice, manual, macwire, cake, reader) and run it:
sbt> project guice sbt> ~run
If you have never worked with Play before, downloading all the dependencies may take several minutes. After the command completes, the application should be available at http://localhost:9000. The application is not a big deal, but you can experiment with the code, modify it and just refresh the page – sbt will recompile necessary files and reload the application automatically.
Notes
a. It may actually be better to define multiple modules instead of embedding logic in the module[7]
References
- Guice contributors. Guice User’s Guide: Just-in-time Bindings
- Guice contributors. Guice User’s Guide: Minimize mutability
- Bloch, Joschua. Effective Java (Second Edition). Addison-Weasley, 2001: 73 – 80
- Fowler, Martin. Inversion of Control Containers and the Dependency Injection pattern
- Fowler, Martin. Anemic Domain Model
- Link, René. Anemic vs. Rich Domain Models
- Guice contributors. Guice User’s Guide: Avoid conditional logic in modules