How can Slick use different Database driver based on Application environment (e.g. test, prod, etc.) - scala

Slick 3.0.0
play 2.6.2
I am experimenting with Slick and run into an interesting issue. I hope the solution is just trivial and I am thinking too much about this
I have implemented the following simple code.
case class Content(content:String)
class ContentTable(tag: Tag) extends Table[Content](tag, "content"){
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def content = column[String]("content")
override def * : ProvenShape[Content] = (content).mapTo[Content]
}
object ContentDb {
val db = Database.forConfig("databaseConfiguration")
lazy val contents = TableQuery[ContentTable]
def all: Seq[Content] = Await.result(db.run(contents.result), 2 seconds)
}
So for this code to work, it requires the following import among others of course.
import slick.jdbc.H2Profile.api._
or alternatively
import slick.jdbc.PostgresProfile.api._
Now, I think, and please correct me if I am wrong, that the database driver should be a configuration detail. That is, I'd choose to run H2 in memory database while developing. Run against test PostgreSQL instance when testing and then run against another instance when in production. I mean the whole idea of abstracting the driver is to have this flexibility... I think.
Now, I have done some research and found that I could do something like this:
trait DbComponent {
val driver: JdbcProfile
import driver.api._
val db: Database
}
trait H2DbComponent extends DbComponent {
val driver: JdbcProfile = slick.jdbc.H2Profile
import driver.api._
val db = Database.forConfig("databaseConfiguration")
}
trait Contents {
def all: Seq[Content]
}
object Contents {
def apply: Contents = new ContentsDb with H2DbComponent
}
trait ContentsDb extends Contents {
this: DbComponent =>
import driver.api._
class ContentTable(tag: Tag) extends Table[Content](tag, "content") {
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def content = column[String]("content")
override def * : ProvenShape[Content] = content.mapTo[Content]
}
lazy val contents = TableQuery[ContentTable]
def all: Seq[Content] = Await.result(db.run(contents.result), 2 seconds)
}
Then I can use dependency injection to inject the right instance for each entity that I have. Not ideal, but possible. So, I start digging on how to have conditional dependency injection based on which environment is running in Play Framework.
I was expecting something similar to the following:
#Component(env=("prod","test")
class ProductionContentsDb extends ContentsDb with PostgresDbComponent
#Component(env="dev")
class ProductionContentsDb extends ContentsDb with H2DbComponent
But, no luck...
EDIT
Just after I have finished writing this and started reading it again, I am curious if we can just have the something similar to:
class DbComponent #Inject (driver: JdbcProfile) {
import driver.api._
val db = Database.forConfig("databaseConfiguration")
}

You can create separate configuration files for each environment. like
application.conf --local
application.prod.conf -- prod with contents below
include "aplication.conf"
###update slick configurations
Then while running application in different stages feed in with -Dconfig.resource=

Related

Slick PostgreSQL Integration

I'm new to scala and I'm trying to integrate a PostgreSQL database to a Lagom application written in scala.I'm trying to utilise the persistence API of Lagom. Lagom has inbuilt support for slick.
My table has 3 fields id of type int, name of type string, data of type jsonb
Since Slick doesn't support json format I'm trying to use slick-pg .
Below is my implementation
My custom profile class
import com.github.tminglei.slickpg.{ExPostgresProfile, PgPlayJsonSupport}
import play.api.libs.json.JsValue
import slick.basic.Capability
import slick.jdbc.{JdbcCapabilities, PostgresProfile}
trait CustomPostgresProfile extends ExPostgresProfile with PgPlayJsonSupport {
def pgjson = "jsonb"
override protected def computeCapabilities: Set[Capability] =
super.computeCapabilities + JdbcCapabilities.insertOrUpdate
override val api = PostgresJsonSupportAPI
object PostgresJsonSupportAPI extends API with JsonImplicits {}
}
object CustomPostgresProfile extends PostgresProfile
My table definition
import com.custom.persistence.profile.CustomPostgresProfile.api._
import play.api.libs.json._
case class CustomDataEntity(id:int,name: String, data: JsValue)
object CustomDataTableDef {
val data = TableQuery[CustomDataTableDef]
}
class CustomDataTableDef(tag: Tag) extends Table[CustomDataEntity](tag, "custom"){
def id = column[Int]("id", O.PrimaryKey)
def name = column[String]("name")
def data = column[JsValue]("data")
override def * =
(id,name,data) <> (CustomDataEntity.tupled,CustomDataEntity.unapply(_))
}
when I'm trying to compile the code, I get the below 2 errors
could not find implicit value for parameter tt: slick.ast.TypedType[play.api.libs.json.JsValue]
[error] def data = column[JsValue]("data")
Cannot resolve symbol <>
Please help me to resolve this
Your object CustomPostgresProfile extends PostgresProfile instead of CustomPostgresProfile. If you fix that, it works.

Sharing my database layer with both play and akka-http projects

I have a play app that is using play-slick (postgresql)
I want to create another akka-http application that will be a sbt multi-project setup with the following:
play application
akka http
shared db library that both play and akka http will use
My play app is using play-slick, and my DAO/table classes look like this:
#Singleton
class AccountDAO #Inject() (protected val dbConfigProvider: DatabaseConfigProvider)
extends HasDatabaseConfigProvider[JdbcProfile] {
import driver.api._
private val accounts = TableQuery[AccountsTable]
def id = column[Int]("id", O.PrimaryKey)
def name = column[String]("name")
def * = ....
}
Now I am confused how I would use this same object in my akka-http project because it will not be using the play-slick library,
and the DatabaseConfigProvider seems to be from the play-slick library.
I looked at an akka-http template that I like but they are wiring up slick differently:
class DatabaseService(jdbcUrl: String, dbUser: String, dbPassword: String) {
private val hikariConfig = new HikariConfig()
hikariConfig.setJdbcUrl(jdbcUrl)
hikariConfig.setUsername(dbUser)
hikariConfig.setPassword(dbPassword)
private val dataSource = new HikariDataSource(hikariConfig)
val driver = slick.driver.PostgresDriver
import driver.api._
val db = Database.forDataSource(dataSource)
db.createSession()
}
github Reference
So they have this DatabaseService class that they pass as a parameter into their DAO/Service layer like: https://github.com/ArchDev/akka-http-rest/blob/master/src/main/scala/me/archdev/restapi/services/UsersService.scala#L9
And their tables are traits like the UserEntityTable
Is it possible to change my code in a way that I can re-use my database layer in both the play and akka-http projects?

Scala Slick - Infinite recursion (StackOverflowError) on query results

I am playing around with Slick on top of a Twitter Finatra application. Finally I thought I made it but now, when I want to process a result, I always get an recursion error. I looked around but I did not find anything helpful for this particular problem. The code I have is actually quite simple:
Map the Database class to a custom Type:
package com.configurationManagement.library
package object Types {
type SlickDatabase = slick.driver.MySQLDriver.api.Database
}
Model:
package com.configurationManagement.app.domain
import slick.lifted.Tag
import slick.driver.MySQLDriver.api._
import slick.profile.SqlProfile.ColumnOption.NotNull
case class ConfigurationTemplate(id: Option[Int], name: String)
class ConfigurationTemplates(tag: Tag) extends Table[ConfigurationTemplate](tag: Tag, "configuration_template") {
def id = column[Int]("id", O.AutoInc, O.PrimaryKey)
def name = column[String]("name", NotNull)
def uniqueNameIndex = index("unique_name", name, unique = true)
def * = (id.?, name) <> (ConfigurationTemplate.tupled, ConfigurationTemplate.unapply)
}
Controller:
package com.configurationManagement.app.controller
import com.google.inject.{Inject, Singleton}
import com.configurationManagement.app.domain.ConfigurationTemplates
import com.configurationManagement.app.dto.request.RequestConfigurationTemplateDto
import com.configurationManagement.library.Types._
import com.twitter.finatra.http.Controller
import com.twitter.inject.Logging
import slick.driver.MySQLDriver.api._
#Singleton
class ConfigurationTemplateController #Inject()(database: SlickDatabase)
extends Controller with Logging with FutureConverter {
post("/configurations/templates") { dto: RequestConfigurationTemplateDto =>
val templates = TableQuery[ConfigurationTemplates]
val query = templates.filter(_.id === 1)
response.ok(query.map(_.name))
}
}
And here comes the error
Infinite recursion (StackOverflowError) (through reference chain: com.configurationManagement.app.domain.ConfigurationTemplates["table_node"]->slick.ast.TableNode["driver_table"]->com.configurationManagement.app.domain.ConfigurationTemplates["table_node"]->slick.ast.TableNode["driver_table"]->com.configurationManagement.app.domain.ConfigurationTemplates["table_node"]->slick.ast.TableNode["driver_table"]->com.configurationManagement.app.domain.ConfigurationTemplates["table_node"]->slick.ast.TableNode["driver_table"]->com.configurationManagement.app.domain.ConfigurationTemplates["table_node"]->slick.ast.TableNode["driver_table"]->com.configurationManagement.app.domain.ConfigurationTemplates["table_node"]->slick.ast.TableNode["driver_table"]->com.configurationManagement.app.domain.ConfigurationTemplates["table_node"]->slick.ast.TableNode["driver_table"]->com.configurationManagement.app.domain.ConfigurationTemplates["table_node"]->slick.ast
Obvisously this line causes the error:
query.map(_.name)
Two things I see, first you need to add .result to the query to transform it into a FixedSqlStreamingAction, second you need a database to run that query on:
private[this] val database: slick.driver.MySQLDriver.backend.DatabaseDef = Database.forDataSource(...)
database.run(templates.filter(_.id === 1).map(_.name).result)
Which returns a Future[Seq[String]], this probably is the expected type from response.ok.

Deleting is not working with Play! 2.4, Slick 3 and PostgreSQL

I have read this SO post: Play 2.4 - Slick 3.0.0 - DELETE not working
but it seems not to work in my case.
I manage to insert or get some data from my PostgreSQL (9.1) database in my Play! 2.4 application, but I don't manage to perform some deletions.
Here is the very simple code of my controller:
package controllers
import javax.inject.Inject
import play.api.db.slick.DatabaseConfigProvider
import play.api.mvc._
import slick.driver.JdbcProfile
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import slick.driver.PostgresDriver.api._
import models.Address
class Application #Inject()(dbConfigProvider: DatabaseConfigProvider) extends Controller {
val dbConfig = dbConfigProvider.get[JdbcProfile]
import dbConfig._
import dbConfig.driver.api._
def index = Action.async {
db.run(addresses.filter(_.id === 1L).delete) map { res =>
Ok(Json.toJson(res))
}
}
}
and my model Address:
case class Address(id: Option[Long], city: String)
object Address {
class Addresses(tag: Tag) extends Table[Address](tag, "addresses") {
def id = column[Long]("addressid", O.PrimaryKey, O.AutoInc)
def city = column[String]("city")
def * = (id.?, city) <>
((Address.apply _).tupled, Address.unapply)
}
lazy val addresses = TableQuery[Addresses]
}
The compilation error I get is: value delete is not a member of slick.lifted.Query[models.Address.Addresses,models.Address.Addresses#TableElementType,Seq]
I suspect that it comes from the imports, but I tried a lot of things without any improvement.
I suspect that the problem is that you are importing the implicits needed for the delete function twice, thus generating an ambiguity and causing the compiler to give up on that:
import slick.driver.PostgresDriver.api._
import dbConfig.driver.api._
You don't probably need one of the two anyway, if you are trying to be generic (e.g. because you want to run tests over H2) try removing the PostgresDriver import. If that doesn't work try removing the inner import and leave PostgresDriver as the SO post you linked seemed to suggest that delete was (is?) not in the common API.

Best Practise of Using Connection Pool in Slick 3.0.0 Together with Play Framework

I followed the documentation of Slick 3.0.0-RC1, using Typesafe Config as database connection configuration. Here is my conf:
database = {
driver = "org.postgresql.Driver"
url = "jdbc:postgresql://localhost:5432/postgre"
user = "postgre"
}
I established a file Locale.scala as:
package models
import slick.driver.PostgresDriver.api._
import scala.concurrent.Future
case class Locale(id: String, name: String)
class Locales(tag: Tag) extends Table[Locale](tag, "LOCALES") {
def id = column[String]("ID", O.PrimaryKey)
def name = column[String]("NAME")
def * = (id, name) <> (Locale.tupled, Locale.unapply)
}
object Locales {
private val locales = TableQuery[Locales]
val db = Database.forConfig("database")
def count: Future[Int] =
try db.run(locales.length.result)
finally db.close
}
Then I got confused that when and where the proper time is to create Database object using
val db = Database.forConfig("database")
If I create db like this, there will be as many Database objects as my models. So what is the best practice to get this work?
You can create an Object DBLocator and load it using lazy operator so that its loaded only on demand.
You can always invoke the method defined in DBLocator class to get an instance of Session.