Search code examples
scalaplayframeworkguiceslick

Scala Play Framework 2.6, Guice, Slick 3.2.3 change database dynamically based on requests


I have an application these are the stack

  • Play 2.6
  • Guice
  • Play Slick 3.0.0

I am trying to query different databases dynamically based on requests parameters.

For example in the application.conf

slick.dbs.default.profile = "slick.jdbc.H2Profile$"
slick.dbs.default.db.driver="org.h2.Driver"
slick.dbs.default.db.url="jdbc:h2:mem:test;MODE=MYSQL;DATABASE_TO_UPPER=false;DB_CLOSE_DELAY=-1;"

play.evolutions.db.default.enabled = true

slick.dbs.mydb2.profile = "slick.jdbc.H2Profile$"
slick.dbs.mydb2.db.driver="org.h2.Driver"
slick.dbs.mydb2.db.url="jdbc:h2:mem:test;MODE=MYSQL;DATABASE_TO_UPPER=false;DB_CLOSE_DELAY=-1;"

play.evolutions.db.mydb2.enabled = true

Repository

@Singleton
class UserRepositoryImpl @Inject() (@NamedDatabase("mydb2") dbConfigProvider: DatabaseConfigProvider)
  (implicit ec: ExecutionContext) extends UserRepository {

  val dbConfig = dbConfigProvider.get[JdbcProfile]

  import dbConfig._
  import profile.api._

  override def all: EitherT[Future, String, Seq[User]] = EitherT {
    db.run(UserTable.UserQuery.result.asTry).map {
      case Success(result) => {
        logger.debug(s"all success: $result")
        Right(result)
      }
      case Failure(error) => {
        val str = s"all error: ${error.getMessage}"
        logger.error(str, error)
        Left(str)
      }
    }
  }

}

Is there a way to change the dbConfigProvider for each request? I would want to change the @NamedDatabase parameter based on the request parameters


Solution

  • Here is on of possible solutions.

    Configure db

    class Module(environment: Environment, configuration: Configuration) extends AbstractModule {
    
      def configure(): Unit = {
        bind(classOf[Config])
          .annotatedWith(Names.named("dbConfig"))
          .toInstance(configuration.underlying.getConfig("slick.dbs"))
    
        bind(classOf[String])
          .annotatedWith(Names.named("dbSchema1"))
          .toInstance("default")
    
        bind(classOf[String])
          .annotatedWith(Names.named("dbSchema2"))
          .toInstance("mydb2")
      }
    }
    

    Create helper class

    @Named("databaseService")
    @Singleton
    class DatabaseService @Inject()(@Named("dbConfig") dbConfig: Config,
                                    @Named("dbSchema1") schema1: String,
                                    @Named("dbSchema2") schema2: String) extends Logging {
    
      lazy val db1: JdbcBackend#DatabaseDef = {
        info("Init DB1")
        val database = DatabaseConfig.forConfig[MySQLProfile](schema1, dbConfig).db
        info("DB1 is ready")
        database
      }
    
      lazy val db2: JdbcBackend#DatabaseDef = {
        info("Init DB2")
        val database = DatabaseConfig.forConfig[MySQLProfile](schema2, dbConfig).db
        info("DB2 is ready")
        database
      }
    
      def run[R](action: DBIOAction[R, NoStream, Nothing])(implicit db: JdbcBackend#DatabaseDef): Future[R] = {
        import scala.concurrent.ExecutionContext.Implicits.global
        db.run(action).recoverWith {
          case exception: Throwable => {
            error("query failed", exception)
            Future.failed[R](exception)
          }
        }
      }
    
      def runDb1[R](action: DBIOAction[R, NoStream, Nothing]): Future[R] = {
        implicit val db = db1
        run(action)
      }
    
      def runDb2[R](action: DBIOAction[R, NoStream, Nothing]): Future[R] = {
        run(action)(db2)
      }
    }
    

    Inject dependency and select db to run

    @Singleton
    class UserRepositoryImpl @Inject() (databaseService: DatabaseService)
                                       (implicit ec: ExecutionContext) extends UserRepository {
    
      def all(param: Param): EitherT[Future, String, Seq[User]] = EitherT {
        val db = param match {
          case db1 => databaseService.db1
          case db2 => databaseService.db2
        }
        databaseService.run(UserTable.UserQuery.result.asTry)(db).map {
          case Success(result) => {
            logger.debug(s"all success: $result")
            Right(result)
          }
          case Failure(error) => {
            val str = s"all error: ${error.getMessage}"
            logger.error(str, error)
            Left(str)
          }
        }
      }
    }