Taylor R
Taylor R

Reputation: 397

How do make a `CustomExecutionContext` available for dependency injection in a Play 2.6 controller?

I'm following along with Play 2.6's Scala documentation and sample code for creating non-blocking actions, and am running into some runtime issues. I have created a new Play application using the Scala template (sbt new playframework/play-scala-seed.g8).

The code that the Play documentation suggests should work in a new controller is (this code is taken verbatim from the Play documentation page, with some extra imports from me):

// some imports added by me to get the code to compile
import javax.inject.Inject
import scala.concurrent.ExecutionContext
import scala.concurrent.Future

import akka.actor.ActorSystem
import play.api.libs.concurrent.CustomExecutionContext
import play.api.mvc._
import play.api.mvc.ControllerComponents
// end imports added by me

import play.api.libs.concurrent.CustomExecutionContext

trait MyExecutionContext extends ExecutionContext

class MyExecutionContextImpl @Inject()(system: ActorSystem)
  extends CustomExecutionContext(system, "my.executor") with MyExecutionContext

class HomeController @Inject()(myExecutionContext: MyExecutionContext, val controllerComponents: ControllerComponents) extends BaseController {
  def index = Action.async {
    Future {
      // Call some blocking API
      Ok("result of blocking call")
    }(myExecutionContext)
  }
}

Then, according to the documentation for using other thread pools, I've defined the my.executor thread pool in the application.conf file of my application:

my.executor {
  fork-join-executor {
    parallelism-factor = 20.0
    parallelism-max = 200
  }
}

I should note that I do not want to use the default execution context as I want to prepare for running futures in a separate context that may be used for a limited resource like a database connection pool.

All of this compiles just fine with sbt compile. However, when I run this with sbt run and access my app in a web browser, I get this error:

CreationException: Unable to create injector, see the following errors:

1) No implementation for controllers.MyExecutionContext was bound. while locating controllers.MyExecutionContext for the 1st parameter of controllers.NewController.(NewController.scala:17) while locating controllers.NewController for the 2nd parameter of router.Routes.(Routes.scala:29) at play.api.inject.RoutesProvider$.bindingsFromConfiguration(BuiltinModule.scala:121): Binding(class router.Routes to self) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$1)

I've used Play 2.3 in the past, and know that dependency injection works when you define an instance of an object (via @Singleton or in a module); however, Play 2.6's documentation on DI indicates that "Guice is able to automatically instantiate any class with an @Inject on its constructor without having to explicitly bind it. This feature is called just in time bindings is described in more detail in the Guice documentation."

My question is: what specific lines of code or configuration do I need to add to Play's own sample to make this work, and why?

Upvotes: 2

Views: 775

Answers (1)

Taylor R
Taylor R

Reputation: 397

I found one possible solution when reading further in the Binding Annotations section of the Scala Dependency Injection documentation page. In particular, it states:

The simplest way to bind an implementation to an interface is to use the Guice @ImplementedBy annotation.

So, by adding that to the my MyExecutionContext trait, like so:

import com.google.inject.ImplementedBy

@ImplementedBy(classOf[MyExecutionContextImpl])
trait MyExecutionContext extends ExecutionContext

an instance of the MyExecutionContextImpl is instantiated and properly injected into the controller.

Too bad that this @ImplementedBy annotation isn't listed in the sample code for the non-blocking action documentation!

Upvotes: 2

Related Questions