1

I'm migrating a Play 2.3.x app to Play 2.5.x and am having some problems using dependency injection.

In 2.3 I had a trait HasRemoteActor which a controller would mix-in to have a reference to some remote actor based on configuration. Since this required the application's config object, it's now required that this becomes a class so the config can be injected. Here's my attempt:

/*
   Dummy controller that has environment and configuration manually injected.
*/
class ConfigurationController(env: play.api.Environment,
                              conf: play.api.Configuration) extends Controller {

}

/*
  Dummy controller that has environment and configuration manually injected, but 
  sets up a remote client.
*/ 
class RemoteActorController(env: play.api.Environment, conf: play.api.Configuration)
  extends ConfigurationController(env, conf) {

  protected val remoteActorName = "foo"
  private val remoteActorConf = conf.underlying.getConfig(remoteActorName)
  private val system = ActorSystem("HttpServerSystem", ConfigFactory.load())

  private val tcpInfo = remoteActorConf.getConfig("akka.remote.netty.tcp")
  private val hostname = tcpInfo.getString("hostname")
  private val port = tcpInfo.getString("port")

  val path = s"akka.tcp://PubSubMember@$hostname:$port/system/receptionist"

  private val initialContacts = Set(ActorPath.fromString(path))


  protected val client = system.actorOf(
    ClusterClient.props(ClusterClientSettings(system).withInitialContacts(
        initialContacts)),
    "ClusterClient"
  )
}

/*
   Actual controller whose actions correspond to endpoints in `conf/routes`.
*/
@Singleton
class BarController @Inject()(env: play.api.Environment,
                              conf: play.api.Configuration) extends
    RemoteActorController(env, conf) {

    // ...

}

However, when I start my application, I find that the actor system always fails to find to its port (even though nothing is listening on that port) irrespective of the port number.

play.api.UnexpectedException: Unexpected exception[ProvisionException: Unable to provision, see the following errors:

1) Error injecting constructor, org.jboss.netty.channel.ChannelException: Failed to bind to: /127.0.0.1:8888

There seems to be a problem with the timing of the injection, but I'm so new to DI that I'm having trouble debugging it.

I tried adding routesGenerator := InjectedRoutesGenerator to my build.sbt and prefixed my injected routes' associated controllers with @, but still find the same runtime exceptions.

Does anyone have suggestions?

erip
  • 16,374
  • 11
  • 66
  • 121
  • "Failed to bind" often means that you already have an application using this port, try to change your configuration to use an other port. – vdebergue Feb 14 '17 at 20:47
  • @vdebergue The ports aren't being used by either TCP or UDP. – erip Feb 15 '17 at 00:59

1 Answers1

1

I would not use inheritance for this. Instead, I would go for something like this (I'm going to assume you're using guice):

@Singleton
class RemoteActorAdapter @Inject() (env: Environment, conf: Configuration) {

  // all other initialization code
  val client: ActorRef = ???

}

In the controller that wants to use these things:

class MyController @Inject() (remoteAdapterProvider: Provider[RemoteActorAdapter]) extends Controller {
  def index = Action {
    remoteAdapterProvider.get.client ! Hello
  }
}

So the trick is that by using a provider, you're deferring the initialization of the binding etc. to the time until it is needed.

erip
  • 16,374
  • 11
  • 66
  • 121
rethab
  • 7,170
  • 29
  • 46
  • While I like this approach more than the one I attempted, I still find that I'm getting bind errors. – erip Feb 15 '17 at 14:28
  • I think it's not binding the `RemoteActorAdapter` as a singleton... do you know how to bind this correctly in `Module.scala`? – erip Feb 15 '17 at 15:02
  • So the purpose of using a `Provider` was to defer the initialization. Does the initialization work the first time, but then fail, because it is not re-using the instance? Or does it also fail on the first attempt? In the former case, you may try something like this: `bind(classOf[RemoteActorAdapter]).to(classOf[RemoteActorAdapter]).in(new SingletonScope())`. In the latter case, I doubt this has anything to do with dependency injection. – rethab Feb 16 '17 at 06:19
  • I think I found the problem... it was totally disjoint from the DI. Rather, it was the `ConfigFactory.load()` call -- this was causing a problem and showing up as a bind error! – erip Feb 16 '17 at 12:32