Custom Router in Playframework 2.4 - scala

I'm using Play 2.4. I'd like to replace the default router, with my own class, using the new dynamic dependency injection play feature. What are the steps to do that?

One possible solution would be to create a new Guice Module, to bind your new router:
class RouterModule extends AbstractModule {
override def configure(): Unit = {
bind(classOf[Router]).to(classOf[CustomRouter])
}
}
Then define a new Application Loader, which will override the default configured router, by using the newly created module:
class MyApplicationLoader extends GuiceApplicationLoader with GuiceableModuleConversions {
override protected def overrides(context: Context): Seq[GuiceableModule] = {
Seq(fromGuiceModule(new RouterModule)) ++ super.overrides(context)
}
}
And use the newly created application loader, instead of the default one, in application.conf:
play.application.loader = "de.zalando.store.pdp.modules.MyApplicationLoader"

Related

Creating a AbstractModule to inject a dependency for a 3rd party library

I have a 3rd party library that I am trying to inject the configuration into the constructor.
This is what I need to do:
class MyModule(configuration: Configuration) extends AbstractModule {
override def configure(): Unit = {
bind(classOf[TwitterApi])
.to(classOf[MyTwitterApi])
.asEagerSingleton
}
}
The constructor of MyTwitterApi doesn't take a Play.api.Configuration but a typesafe.config.Config
class MyTwitterApi(config: Config) ...
So I need to do pass configuration.underlying to my constructor, how is this possible using DI in this AbstractModule?
I need this instance to be a singleton also.
You can use provider to setup your module with eagerSingleton
import com.google.inject.{AbstractModule, Provider}
class MyModule(configuration: Configuration) extends AbstractModule {
override def configure(): Unit = {
val twitterApiProvider: Provider[TwitterApi] =
() => new MyTwitterApi(configuration.underlying)
bind(classOf[TwitterApi])
.toProvider(twitterApiProvider)
.asEagerSingleton
}
}
You can find a working example with sample classes at - https://scastie.scala-lang.org/sarveshseri/ujwvJJNnTpiWDqdkBJQoFw/2
I think you want something like this:
class MyModule(configuration: Configuration) extends AbstractModule {
override def configure(): Unit = {
val myTwitterApiInstance = new MyTwitterApi(configuration.underlying)
bind(classOf[TwitterApi])
.toInstance(myTwitterApiInstance)
}
}
Or another approach would be to provide a binding for Config but if your MyTwitterApi doesn't have #Inject annotation this won't help.

How to create a lagom server with grpc service implementation only?

I am trying to create a grpc service using Lagom framework following this documentation. Here the hello service also exposes rest API besides grpc service.
Now while creating lagom server in the ApplicationLoader we assign grpc service impl as additionalRouter like so:
abstract class HelloApplication(context: LagomApplicationContext)
extends LagomApplication(context)
with AhcWSComponents {
// Bind the service that this server provides
override lazy val lagomServer =
serverFor[HelloService](wire[HelloServiceImpl])
.additionalRouter(wire[HelloGrpcServiceImpl])
}
Its all fine for the purpose of demo but we may not need to always create a REST endpoint besides gRPC endpoint. In that case I won't need either HelloService or HelloServiceImpl. The problem is how would you create lagom server with only HelloGrpcServiceImpl? I can't see to find a way either any documentation or the APIs itself to be able to achieve this!
Please suggest.
Based on the answer in the link I provided as a comment to my question, the solution would look something like this:
trait PrimeGeneratorService extends Service {
override def descriptor: Descriptor = named("prime-generator")
}
class PrimeGeneratorServiceImpl() extends PrimeGeneratorService
abstract class PrimeGeneratorApplication(context: LagomApplicationContext)
extends LagomApplication(context) with AhcWSComponents {
override lazy val lagomServer: LagomServer = {
serverFor[PrimeGeneratorService](wire[PrimeGeneratorServiceImpl])
.additionalRouter(wire[PrimeGeneratorGrpcServiceImpl])
}
}
class PrimeGeneratorLoader extends LagomApplicationLoader {
override def load(context: LagomApplicationContext): LagomApplication =
new PrimeGeneratorApplication(context) {
override def serviceLocator: ServiceLocator = NoServiceLocator
}
override def loadDevMode(context: LagomApplicationContext): LagomApplication =
new PrimeGeneratorApplication(context) with LagomDevModeComponents
override def describeService = Some(readDescriptor[PrimeGeneratorService])
}
Here we have to create dummy service trait and implementation, namely, PrimeGeneratorService and PrimeGeneratorServiceImpl, respectively.
Don't forget to add following in the loader class:
override def describeService = Some(readDescriptor[PrimeGeneratorService])

Manual Dependancy Injection App testing Play 2.5.x

I have project with manual dependency injection. Can I test my application with standard Play test suite?
play.application.loader="AppLoader"
class AppLoader extends ApplicationLoader {
override def load(context: Context): Application = {
LoggerConfigurator(context.environment.classLoader).foreach(_.configure(context.environment))
new AppComponents(context).application
}
}
}
class AppComponents(context: Context) extends BuiltInComponentsFromContext(context) with EhCacheComponents with EvolutionsComponents with DBComponents with HikariCPComponents{
lazy val applicationController = new controllers.Application(defaultCacheApi, dbApi.database("default"))
lazy val usersController = new controllers.Users(defaultCacheApi)
lazy val assets = new controllers.Assets(httpErrorHandler)
//applicationEvolutions
// Routes is a generated class
override def router: Router = new Routes(httpErrorHandler, applicationController, usersController, assets)
For now test is very simple
class ApplicationTest extends PlaySpec with OneAppPerTest {
"Application" must {
"send 404 on a bad request" in {
route(FakeRequest(GET, "/boum")) mustBe None
}
}
}
Test ends up with error:
Could not find a suitable constructor in controllers.Application. Classes must have either one (and only one) constructor annotated with #Inject or a zero-argument
I presume I need somehow use my AppLoader instead of defualt Guice mechanisam inside ApplicationTest class, because Application controller has dependacy ( cacheApi, dbApi ...)
route method can take application as argument but how can I obtain context to manually instantiate AppLoader class ? Being newbie in Scala recommendations are most welcomed.
This example answered all of my questions:
https://github.com/playframework/play-scala-compile-di-with-tests
Using term compile time dependency injection yield much more results then manual dependency injection.

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.
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:
Scala: Dependency Injection
Scala: Play WS API
play.api.inject.Injector
I saw this awesome post last week: http://www.schibsted.pl/2016/04/dependency-injection-play-framework-scala/

Guice in Scala: Module for a class that has a DI-constructor itself

I'm using codingwell/scala-guice and trying to inject DAO-classes into constructors of other components/classes.
In the first attempt, I only used one DAO-class to see if it works:
class DaoModule extends AbstractModule with ScalaModule {
override def configure() {
val dao1 = new FirstDaoImpl
bind(new TypeLiteral[FirstDaoTrait] {}).toInstance(dao1)
}
}
The binding works as expected, it can be used for constructor injection.
In the second step, I wanted to add another DAO class to the module. However, that DAO-class depends on the first DAO:
class SecondDaoImpl #Inject()(firstDao: FirstDaoTrait) extends SecondDaoTrait
I'm not sure how to add the necessary binding to the existing module. Repeating the first step would result in this:
val dao2 = new SecondDaoImpl(???)
bind(new TypeLiteral[SecondDaoTrait] {}).toInstance(dao2)
But of course this class can only be instantiated by providing the first DAO (therefore the "???"). How can I do this?
Use bind and let scala-guice resolve the dependencies for you:
class DaoModule extends AbstractModule with ScalaModule {
override def configure() {
bind[FirstDaoTrait].to[FirstDaoImpl]
bind[SecondDaoTrait].to[SecondDaoImpl]
}
}
And now using the injector:
val injector = Guice.createInjector(new DaoModule())
val secondDao = injector.instance[SecondDaoTrait]