Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Play Scala Dependency injection: How to use it

I am trying to use Play 2.5 dependency injection. I have following class which makes a call to REST api and parses the response

class Client @Inject()(ws:WSClient, baseUrl: string) {

  def this(ws:WSClient) = this(ws, "<url string>")
  def getResponse() = {....}
....

}

The caller of the code looks like below

var client = new Client(WS.client)
client.getResponse()

I am getting following warning.

object WS in package ws is deprecated: Inject WSClient into your component

I understand that i need to inject WS.Client instead of passing it explicitly to the Client constructor. But how do i do that?

=== Update ===

I don't want to inject Client or WSClient from the Controller. My controller creates objects and classes at run time and i want those objects to create Client Object. When i explicitly pass WS.client object to the Client object i get the above stated warning.

=== Update 2 ===

I have a plugin architecture in my application. When a a controller starts an action. It does not know what set of plugins it is going to execute. Some plugins would not need a WSClient and some of them would. So i dont want to couple the injection of WSClient into my controller. Each plugin independently decides if it wants to call a remote service. When a plugin decides to call the remote service, it should be able to inject WSClient in what ever client it wants to invoke.

Controller Action --> Determine Plugins to Execute --> Execute Plugins ---> Plugin1 (needs to call a remote api, create a client object, per say new Client(WS.Client)). This is where the injection should happen, not at the controller.

like image 734
konquestor Avatar asked Apr 18 '16 01:04

konquestor


2 Answers

Ok. I will assume you have two classes. First we will have your Client class:

@Singleton // this is not necessary, I put it here so you know this is possible
class Client @Inject() (ws:WSClient, baseUrl: String) {

    // Since this controller is not annotated with @Inject
    // it WILL NOT be used when binding components
    def this(ws:WSClient) = this(ws, "<url string>")

    def getResponse() = {
        // do something using ws object
    }
}

Then you have another class that uses Client, per instance, a controller:

class MyController @Inject() (client: Client) extends Controller {

    def someAction = Action {
        // do something with client object
    }

}

The main point here is that the controller did not need to create a Client instance. It was automatically injected by Guice.

Moreover, your client class needs a baseUrl and there is no place telling Play which value is needed there. If this is a configuration, than you can do something like this:

import play.api.Configuration

class Client @Inject() (ws:WSClient, configuration: Configuration) {

    def getResponse() = {
        val baseUrl = configuration.getString("key.to.baseUrl")
        // do something using ws object and baseUrl
    }
}

But, if you really want your Client object to receives a String, then we need to tell Play which String needs to be injected:

package com.acme.modules

import com.google.inject.AbstractModule
import com.google.inject.name.Names

class MyModule extends AbstractModule {
  def configure() = {
    bind(classOf[String])
      .annotatedWith(Names.named("baseUrl")) // attention to the name here. It will be used below
      .toInstance("http://api.example.com/")
  }
}

And then enable this module by adding the following line to your application.conf:

play.modules.enabled += "com.acme.modules.MyModule"

After that, we will change Client to be specific about which String it is expecting:

import play.api.Configuration

// @Named needs to receive the same value defined at the module class.
class Client @Inject() (ws:WSClient, @Named("baseUrl") baseUrl: String) {

    def getResponse() = {
        val baseUrl = configuration.getString("key.to.baseUrl")
        // do something using ws object and baseUrl
    }
}

Update after question edit:

Give the structure you want/need:

Controller Action --> Determine Plugins to Execute --> Execute Plugins ---> Plugin1

Your code can also follow that path with classes like this:

MyController -> PluginResolver -> Plugin
             -> PluginRunner ->

And, then, you can have:

Controller:

class MyController @Inject() (
    pluginResolver: PluginResolver,
    pluginRunner: PluginRunner
) extends Controller {

    def action = Action {
        val plugins = pluginsResolver.resolve(/* give a criteria to select plugins */)
        val someResultFromPluginsExecution = pluginsRunner.run(plugins)

        // map result from plugins execution to a play play.api.mvc.Result
        // return the play.api.mvc.Result
    }
}

Plugin classes:

import play.api.inject.Injector

class PluginResolver @Inject()(injector: Injector) {

    def resolve(/* some criteria to resolve plugins */): Seq[Plugin] = {
        val pluginsClasses = ... // find the necessary plugins based on the criteria
        pluginsClasses.map { pluginClass => injector.instanceOf(pluginClass) }
    }

}

// ExecutionContext is not really necessary, but maybe you want/need
// another thread pool to execute plugins
class PluginRunner @Inject()(implicit executionContext: ExecutionContext) {

    def run(plugins: Seq[Plugin]): Seq[PluginExecutionResult] = {
        // run the plugins
        // return the result
    }
}

trait Plugin {
    def execute(): PluginExecutionResult
}

The real magic here happens at the PluginResolver. It uses a play.api.inject.Injector to create plugins instances and then your plugins can use Dependency Injection. Per instance:

class PluginThatNeedsWSClient @Inject(wsClient: WSClient) extends Plugin {
    def execute(): PluginExecutionResult = {
        // Use wsClient to call a remote service
        // return the execution result
    }
}

Reference:

  1. Scala: Dependency Injection
  2. Scala: Play WS API
  3. play.api.inject.Injector
like image 81
marcospereira Avatar answered Oct 21 '22 10:10

marcospereira


I saw this awesome post last week: http://www.schibsted.pl/2016/04/dependency-injection-play-framework-scala/

like image 35
pedrorijo91 Avatar answered Oct 21 '22 10:10

pedrorijo91