zoran jeremic
zoran jeremic

Reputation: 2138

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?

Upvotes: 1

Views: 216

Answers (1)

Ga&#235;l J
Ga&#235;l J

Reputation: 15305

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

Upvotes: 0

Related Questions