PlayFramework Scala dependency Injection Javax - scala

I am new to Scala and the PlayFramework and am trying to figure out how I can do a a dependency Injection. I basically want a file that will be a trait and inject that into a controller. My problem is that my Controller class is not seeing my Trait this is my code
ProfileTrait
package traitss
import play.api.mvc._
trait ProfileTrait extends Controller {
def Addone()
}
Then I try to inject that into my controller
import java.nio.file.{Files, Paths}
import traitss.ProfileTrait_
import play.api.mvc.{Action, Controller}
import javax.inject._
class Profiles #Inject() (profileTrait: ProfileTrait) extends Controller
{
}
However my controller is not seeing it, I am trying to follow the example here https://www.playframework.com/documentation/2.5.x/ScalaDependencyInjection .
I am using the play framework version 2.50

You cannot inject a trait directly. You need to specify the implementation of the trait that needs to be injected.
There are two ways to specify the implementation of a trait that is to be injected:
Using #ImplementedBy annotation. Here's a simple example:
package traitss
import play.api.mvc._
import com.google.inject.ImplementedBy
#ImplementedBy(classOf[ProfileTraitImpl])
trait ProfileTrait extends Controller {
def Addone()
}
class ProfileTraitImpl extends ProfileTrait {
// your implementation goes here
}
Using Programmatic Bindings
package traitss
import play.api.mvc._
import com.google.inject.ImplementedBy
#ImplementedBy(classOf[ProfileTraitImpl])
trait ProfileTrait extends Controller {
def Addone()
}
ProfileTraitImpl:
package traitss.impl
class ProfileTraitImpl extends ProfileTrait {
// your implementation goes here
}
Create a module where you can bind the implementation with trait
import com.google.inject.AbstractModule
class Module extends AbstractModule {
def configure() = {
bind(classOf[ProfileTrait])
.to(classOf[ProfileTraitImpl])
}
}
With using the modules approach you get an additional benefit of enabling or disabling the binding. For example, in your application.conf file you can enable/disable a module using play.modules.enabled += module OR play.modules.disabled += module

You can't inject a trait, you have to inject an object that implements that trait.
For dependency injection to work, you have to tell the framework (play uses Guice under the hood) how to resolve the dependency to be injected.
There are many ways to do it and it depends on your case, for more detail you can look at Guice's documentation, the simplest way in play is to create Module.scala into your app directory , if it's not there already, and put something like this:
import com.google.inject.AbstractModule
class Module extends AbstractModule {
override def configure() = {
bind(classOf[ProfileTrait]).toInstance( ... )
}
}
where in ... you put the logic to create the object you want to inject.

Related

How to bind a class that extends a trait with a monadic type in Scala Guice

I've created simple Scala Play application that has traits with a monadic type and their respective implementation binded in Module configure as:
class Module extends AbstractModule {
override def configure() = {
bind(new TypeLiteral[UserDAO[DBIO]](){}).to(classOf[UserDAOImpl])
bind(new TypeLiteral[UserService[Future]](){}).to(classOf[UserServiceImpl[Future, DBIO]])
}
}
Those traits and implementations are:
///TRAITS
//UserDAO.scala
package models
trait UserDAO[DB[_]] {
def get(userId: Long): DB[Option[User]]
}
//UserService.scala
package services
import resources.UserResponse
import services.response.ServiceResponse
trait UserService[F[_]] {
def findUserById(id: Long): F[ServiceResponse[UserResponse]]
}
///IMPLEMENTATIONS
//UserDAOImpl.scala
package dao
import models.{DataContext, User, UserDAO}
import play.api.db.slick.DatabaseConfigProvider
import slick.dbio.{DBIO => SLICKDBIO}
import javax.inject.Inject
import scala.concurrent.ExecutionContext
class UserDAOImpl #Inject()(
protected val dbConfigProvider: DatabaseConfigProvider,
val context: DataContext
)(
implicit executionContext: ExecutionContext
) extends UserDAO[SLICKDBIO] {
import context.profile.api._
override def get(userId: Long): SLICKDBIO[Option[User]] = context.Users.filter(_.id === userId).result.headOption
}
//UserServiceImpl.scala
package services
import resources.Mappings.UserToResponseMapping
import cats.Monad
import cats.implicits._
import models.{DatabaseManager, UserDAO}
import resources.{NoModel, UserResponse}
import services.response.ServiceResponse
import util.Conversions.{errorToServiceResponse, objectToServiceResponse}
import javax.inject.Inject
class UserServiceImpl[F[_]: Monad, DB[_]: Monad]#Inject()(userRepo: UserDAO[DB],
dbManager: DatabaseManager[F, DB])
extends UserService[F] {
override def findUserById(id: Long): F[ServiceResponse[UserResponse]] = {
for {
user <- dbManager.execute(userRepo.get(id))
} yield user match {
case Some(user) =>user.asResponse.as200
case None => NoModel(id).as404
}
}
}
However, this fails to inject dependencies and throws the following errors:
play.api.UnexpectedException: Unexpected exception[CreationException: Unable to create injector, see the following errors:
1) models.UserDAO<DB> cannot be used as a key; It is not fully specified.
at services.UserServiceImpl.<init>(UserServiceImpl.scala:13)
at Module.configure(Module.scala:35) (via modules: com.google.inject.util.Modules$OverrideModule -> Module)
2) models.DatabaseManager<F, DB> cannot be used as a key; It is not fully specified.
at services.UserServiceImpl.<init>(UserServiceImpl.scala:13)
at Module.configure(Module.scala:35) (via modules: com.google.inject.util.Modules$OverrideModule -> Module)
3) cats.Monad<F> cannot be used as a key; It is not fully specified.
at services.UserServiceImpl.<init>(UserServiceImpl.scala:13)
at Module.configure(Module.scala:35) (via modules: com.google.inject.util.Modules$OverrideModule -> Module)
This question might be related to this one How to bind a class that extends a Trait with a monadic type parameter using Scala Guice?, and in my solution I've applied what is suggested as the answer, but it still fails.
Any suggestions?
If you look at the stacktrace, you can see the issue happens when Guice wants to create an instance of UserServiceImpl:
... at services.UserServiceImpl.<init> ...
I suspect that Guice cannot know what to "inject" when trying to create this class. It cannot infer that it has to inject a UserDao[DBIO] for instance, it only knows it has to inject a UserDao[DB] with DB being something unspecified.
How to fix that, I can't say for sure but I would look into either:
adding "concrete" class for UserServiceImpl and bind it instead of the generic one (like a class UserServiceFutureDBIO)
manually instantiating a UserServiceImpl and binding to an instance rather than binding to a class and letting Guice instantiate it

Accessing Singleton values in Play for Scala

I have the following singleton defined in Scala
package main
import javax.inject._
#Singleton
class Properties {
val timeout = 120
}
how do I access it from other programs? I tried main.Properties.timeout but it throws a compilation error saying that a companion object was not found
If you want to access it in the way, that you've mentioned: main.Properties.timeout, then use companion object instead:
class Properties {
// ...
}
object Properties {
val timeout = 120
// ...
}
With #Singleton annotation, you have to inject that service somewhere, to be able to use it. So something like this:
import javax.inject._
import main.Properties
class SomeService #Inject() (props:Properties)() {
println(props.timeout)
}
Here is documentation about DI for PlayFramework: https://www.playframework.com/documentation/2.5.x/ScalaDependencyInjection - for the latest one (not 2.0), but it is a good start point.

Play Framework 2.5.x: Inject Environment in a Module

Play app contains a custom module that initializes DB/Tables during the app start.
Starting with v2.5.x, the application mode(prod, test, dev) needs to be injected rather than using Current. I tried to apply the following inject parameters in the module code but saw compile errors.
trait MyInit[Environment] {
val env: Environment
}
class MyInitClass[Environment] #Inject() (
val env: Environment
) extends MyInit[Environment] {
initializeDB()
def initializeDB() = {
Logger.info("MyINIT: Creating database...")
MyDBService.createDatabase()
Logger.info("MyINIT: Creating tables...")
MyDBService.createTables()
}
}
class MyModule(environment: Environment,
configuration: Configuration) extends AbstractModule with ScalaModule {
def configure() = {
bind[MyInit[Environment]].to[MyInitClass[Environment]].asEagerSingleton()
}
}
Is there a prescribed way of looking up Environment/Mode during module initialization in Play 2.5.x (Scala)?
I will rewrite your code to remove a small possible confusion here. This is exactly your code with a small rename of the type parameters:
import javax.inject.Inject
import play.api.Logger
import play.api.Environment
import play.api.Configuration
import com.google.inject.AbstractModule
trait MyInit[T] {
val env: T
}
class MyInitClass[T] #Inject() (val env: T) extends MyInit[T] {
initializeDB()
def initializeDB() = {
Logger.info("MyINIT: Creating database...")
MyDBService.createDatabase()
Logger.info("MyINIT: Creating tables...")
MyDBService.createTables()
}
}
I renamed the type parameters to avoid confusion with play.api.Environment. I have the felling that this is not what you want, since MyInitClass does not actively receive an play.api.Environment anywhere.
If you want to do some initialization, why not go with something more simple and direct like this:
import com.google.inject.AbstractModule
import play.api.{Configuration, Environment, Mode}
class DatabaseInitializerModule(
environment: Environment,
configuration: Configuration
) extends AbstractModule {
def configure() = {
environment.mode match {
case Mode.Dev =>
// Start dev database
case Mode.Test =>
// Start test database
case Mode.Prod =>
// Start prod database
}
}
}
Edit:
A more idiomatic and recommended way to replace GlobalSettings.onStart is described at the docs:
GlobalSettings.beforeStart and GlobalSettings.onStart: Anything that needs to happen on start up should now be happening in the constructor of a dependency injected class. A class will perform its initialisation when the dependency injection framework loads it. If you need eager initialisation (because you need to execute some code before the application is actually started), define an eager binding.
So, we can rewrite your code to be something like this:
import javax.inject.Inject
import play.api.{Environment, Mode}
import com.google.inject.AbstractModule
class DatabaseInitializer #Inject()(environment: Environment) {
environment.mode match {
case Mode.Dev =>
// Start dev database
case Mode.Test =>
// Start test database
case Mode.Prod =>
// Start prod database
}
}
class DatabaseInitializerModule extends AbstractModule {
override def configure() = {
bind(classOf[DatabaseInitializer]).asEagerSingleton()
}
}
Notice how I've removed the trait since it is not adding any value here.

Guice and Play2 Singleton from trait

I am using Play with Scala and I am trying to create a singleton, and i want to inject it from its trait and not directly.
for example:
#ImplementedBy(classOf[S3RepositoryImpl])
trait S3Repository {
}
#Singleton
class S3RepositoryImpl extends S3Repository {
}
But this fails with error:
trait Singleton is abstract; cannot be instantiated
I have tried several combinations and they all produce the same.
I come from Spring background and its very natural there? am i missing something about how Guice handles this type of Injection?
Thanks.
As pointed out by #Tavian-Barnes, the solution is to ensure you have the following import:
import javax.inject.Singleton
I have here a "complete" working example, just hope that I'm not stating the obvious...
package controllers
import play.api._
import play.api.mvc._
import com.google.inject._
class Application #Inject() (s3: S3Repository) extends Controller {
def index = Action {
println(s3.get)
Ok
}
}
#ImplementedBy(classOf[S3RepositoryImpl])
trait S3Repository {
def get: String
}
#Singleton
class S3RepositoryImpl extends S3Repository {
def get: String = "bla"
}
Whenever you mark a class' constructor with #Inject the Guice will manage the injection of the instance itself. So, if you marked your class as #Singleton, Guice will create and will always give you just that one instance. Nobody can stop you from manually instantiating a class in your code... You can explore it in detail at Play.
Use below import, instead of import javax.inject.Singleton
import com.google.inject.{Inject, Singleton}
Import both Inject and Singleton.
import javax.inject.{Inject, Singleton}
Refrain from using:
import com.google.inject.{Inject, Singleton}
as play framework requires the javax import

How to execute on start code in scala Play! framework application?

I need to execute a code allowing the launch of scheduled jobs on start of the application, how can I do this? Thanks.
Use the Global object which - if used - must be defined in the default package:
object Global extends play.api.GlobalSettings {
override def onStart(app: play.api.Application) {
...
}
}
Remember that in development mode, the app only loads on the first request, so you must trigger a request to start the process.
Since Play Framework 2.6x
The correct way to do this is to use a custom module with eager binding:
import scala.concurrent.Future
import javax.inject._
import play.api.inject.ApplicationLifecycle
// This creates an `ApplicationStart` object once at start-up and registers hook for shut-down.
#Singleton
class ApplicationStart #Inject() (lifecycle: ApplicationLifecycle) {
// Start up code here
// Shut-down hook
lifecycle.addStopHook { () =>
Future.successful(())
}
//...
}
import com.google.inject.AbstractModule
class StartModule extends AbstractModule {
override def configure() = {
bind(classOf[ApplicationStart]).asEagerSingleton()
}
}
See https://www.playframework.com/documentation/2.6.x/ScalaDependencyInjection#Eager-bindings
I was getting a similar error.
Like #Leo said, create Global object in app/ directory.
Only thing I had to make sure was to change "app: Application" to "app: play.api.Application".
app: Application referred to class Application in controllers package.