Many changes
Rewrote slick db service using monix task instead of future Added cats IO to controller to make use of above db service Added action methods that support effect wrappers other than future Added play-flyway to auto-manage migrations Moved migration files to appropriate directory as a result of above Added cors filter to work with separately hosted frontend Made logger log at debug
This commit is contained in:
parent
a9c6cfc96c
commit
cb9524eac7
@ -13,6 +13,11 @@ import com.example.user.CarDAO
|
|||||||
import com.example.Car.slick.SlickCarDAO
|
import com.example.Car.slick.SlickCarDAO
|
||||||
import com.example.services.LibraryService
|
import com.example.services.LibraryService
|
||||||
import com.example.user.slick.services.SlickLibraryService
|
import com.example.user.slick.services.SlickLibraryService
|
||||||
|
import com.example.services.TaskLibraryService
|
||||||
|
import com.example.user.slick.services.TaskSlickLibraryService
|
||||||
|
import monix.execution.Scheduler
|
||||||
|
import util.{Schedulers, SchedulersImpl}
|
||||||
|
import slick.jdbc.JdbcProfile
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This module handles the bindings for the API to the Slick implementation.
|
* This module handles the bindings for the API to the Slick implementation.
|
||||||
@ -23,9 +28,13 @@ class Module(environment: Environment, configuration: Configuration)
|
|||||||
extends AbstractModule {
|
extends AbstractModule {
|
||||||
override def configure(): Unit = {
|
override def configure(): Unit = {
|
||||||
bind(classOf[Database]).toProvider(classOf[DatabaseProvider])
|
bind(classOf[Database]).toProvider(classOf[DatabaseProvider])
|
||||||
|
bind(classOf[JdbcProfile]).toProvider(classOf[JdbcProfileProvider])
|
||||||
bind(classOf[UserDAO]).to(classOf[SlickUserDAO])
|
bind(classOf[UserDAO]).to(classOf[SlickUserDAO])
|
||||||
bind(classOf[CarDAO]).to(classOf[SlickCarDAO])
|
bind(classOf[CarDAO]).to(classOf[SlickCarDAO])
|
||||||
bind(classOf[LibraryService]).to(classOf[SlickLibraryService])
|
bind(classOf[LibraryService]).to(classOf[SlickLibraryService])
|
||||||
|
bind(classOf[TaskLibraryService]).to(classOf[TaskSlickLibraryService])
|
||||||
|
bind(classOf[Scheduler]).toProvider(classOf[SchedulerProvider])
|
||||||
|
bind(classOf[Schedulers]).toProvider(classOf[AppSchedulersProvider])
|
||||||
bind(classOf[DBCloseHook]).asEagerSingleton()
|
bind(classOf[DBCloseHook]).asEagerSingleton()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -35,7 +44,27 @@ class DatabaseProvider @Inject() (config: Config) extends Provider[Database] {
|
|||||||
lazy val get = Database.forConfig("myapp.database", config)
|
lazy val get = Database.forConfig("myapp.database", config)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Closes database connections safely. Important on dev restart. */
|
@Singleton
|
||||||
|
class SchedulerProvider() extends Provider[Scheduler] {
|
||||||
|
lazy val get = Scheduler.io()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class AppSchedulersProvider() extends Provider[Schedulers] {
|
||||||
|
lazy val get =
|
||||||
|
new SchedulersImpl(
|
||||||
|
dbScheduler = Scheduler.io(),
|
||||||
|
cpuScheduler = Scheduler.global
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class JdbcProfileProvider() extends Provider[JdbcProfile] {
|
||||||
|
lazy val get: JdbcProfile = _root_.slick.jdbc.H2Profile
|
||||||
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
class DBCloseHook @Inject() (db: Database, lifecycle: ApplicationLifecycle) {
|
class DBCloseHook @Inject() (db: Database, lifecycle: ApplicationLifecycle) {
|
||||||
|
|
||||||
lifecycle.addStopHook { () => Future.successful(db.close()) }
|
lifecycle.addStopHook { () => Future.successful(db.close()) }
|
||||||
}
|
}
|
||||||
|
@ -9,21 +9,28 @@ import scala.concurrent.ExecutionContext
|
|||||||
import com.example.user.CarDAO
|
import com.example.user.CarDAO
|
||||||
import com.example.services.LibraryService
|
import com.example.services.LibraryService
|
||||||
import play.api.libs.json.Json
|
import play.api.libs.json.Json
|
||||||
|
import com.example.services.TaskLibraryService
|
||||||
|
|
||||||
|
case class UserForm(name: String)
|
||||||
|
object UserForm {
|
||||||
|
implicit val userFormFormat = Json.format[UserForm]
|
||||||
|
}
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class HomeController @Inject() (
|
class HomeController @Inject() (
|
||||||
userDAO: UserDAO,
|
userDAO: UserDAO,
|
||||||
carDAO: CarDAO,
|
carDAO: CarDAO,
|
||||||
libraryService: LibraryService,
|
libraryService: LibraryService,
|
||||||
|
taskLibraryService: TaskLibraryService,
|
||||||
cc: ControllerComponents
|
cc: ControllerComponents
|
||||||
)(implicit ec: ExecutionContext)
|
)(implicit ec: ExecutionContext)
|
||||||
extends AbstractController(cc) {
|
extends AbstractController(cc) {
|
||||||
|
|
||||||
def index = Action.async { implicit request =>
|
def index = Action.async {
|
||||||
userDAO.all.map { users => Ok(views.html.index(users)) }
|
userDAO.all.map { users => Ok(views.html.index(users)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
def cars = Action.async { implicit request =>
|
def cars = Action.async {
|
||||||
carDAO.all.map { cars => Ok(views.html.cars(cars)) }
|
carDAO.all.map { cars => Ok(views.html.cars(cars)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,10 +43,16 @@ class HomeController @Inject() (
|
|||||||
} yield (Ok(Json.toJson(maybeBook)))
|
} yield (Ok(Json.toJson(maybeBook)))
|
||||||
}
|
}
|
||||||
|
|
||||||
def authors(bookId: Long) = Action.async { implicit request =>
|
def authors(bookId: Long) = Action.async {
|
||||||
libraryService.getAuthorsForBook(bookId).map( t => {
|
libraryService
|
||||||
Ok(Json.toJson(t))
|
.getAuthorsForBook(bookId)
|
||||||
})
|
.map(t => {
|
||||||
|
Ok(Json.toJson(t))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
def user = Action {
|
||||||
|
Ok(Json.toJson(UserForm("hello")))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
34
app/controllers/MonixHomeController.scala
Normal file
34
app/controllers/MonixHomeController.scala
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import javax.inject.{Inject, Singleton}
|
||||||
|
import play.api.mvc._
|
||||||
|
import play.api.libs.json.Json
|
||||||
|
import com.example.services.TaskLibraryService
|
||||||
|
import util.IOHttp._
|
||||||
|
import com.typesafe.scalalogging.LazyLogging
|
||||||
|
import cats.effect.IO
|
||||||
|
import scala.concurrent.ExecutionContext
|
||||||
|
import util.Schedulers
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class MonixHomeController @Inject() (
|
||||||
|
taskLibraryService: TaskLibraryService,
|
||||||
|
cc: ControllerComponents
|
||||||
|
)(schedulers: Schedulers, ec: ExecutionContext)
|
||||||
|
extends AbstractController(cc)
|
||||||
|
with LazyLogging {
|
||||||
|
|
||||||
|
private implicit val defaultScheduler = schedulers.dbScheduler
|
||||||
|
|
||||||
|
def authors(bookId: Long) = Action.asyncF {
|
||||||
|
for {
|
||||||
|
_ <- IO(logger.debug("Getting Authors"))
|
||||||
|
authors <- taskLibraryService.getAuthorsForBook(bookId).to[IO]
|
||||||
|
_ <- IO(logger.debug("Where am I now?"))
|
||||||
|
_ <- IO.shift(ec)
|
||||||
|
_ <- IO(logger.info("Got Authors"))
|
||||||
|
res <- IO.pure(Ok(Json.toJson(authors)))
|
||||||
|
} yield (res)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,17 +1,17 @@
|
|||||||
package service
|
// package service
|
||||||
|
|
||||||
import javax.inject._
|
// import javax.inject._
|
||||||
import play.api.db.slick.DatabaseConfigProvider
|
// import play.api.db.slick.DatabaseConfigProvider
|
||||||
import scala.concurrent.ExecutionContext
|
// import scala.concurrent.ExecutionContext
|
||||||
import play.api.db.slick.HasDatabaseConfigProvider
|
// import play.api.db.slick.HasDatabaseConfigProvider
|
||||||
import slick.jdbc.JdbcProfile
|
// import slick.jdbc.JdbcProfile
|
||||||
|
|
||||||
@Singleton
|
// @Singleton
|
||||||
class MyService @Inject() (
|
// class MyService @Inject() (
|
||||||
protected val dbConfigProvider: DatabaseConfigProvider,
|
// protected val dbConfigProvider: DatabaseConfigProvider,
|
||||||
implicit val ec: ExecutionContext
|
// implicit val ec: ExecutionContext
|
||||||
) extends HasDatabaseConfigProvider[JdbcProfile] {
|
// ) extends HasDatabaseConfigProvider[JdbcProfile] {
|
||||||
|
|
||||||
import profile.api._
|
// import profile.api._
|
||||||
db
|
// db
|
||||||
}
|
// }
|
||||||
|
18
app/util/Schedulers.scala
Normal file
18
app/util/Schedulers.scala
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import monix.execution.Scheduler
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class containing various schedulers for offloading blocking tasks
|
||||||
|
* from play's default thread pool
|
||||||
|
*
|
||||||
|
* @param dbScheduler
|
||||||
|
* @param cpuScheduler
|
||||||
|
*/
|
||||||
|
trait Schedulers {
|
||||||
|
def dbScheduler: Scheduler
|
||||||
|
def cpuScheduler: Scheduler
|
||||||
|
}
|
||||||
|
|
||||||
|
class SchedulersImpl(val dbScheduler: Scheduler, val cpuScheduler: Scheduler)
|
||||||
|
extends Schedulers
|
58
app/util/Util.scala
Normal file
58
app/util/Util.scala
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import scala.concurrent.Future
|
||||||
|
import cats.effect.IO
|
||||||
|
import cats.implicits._
|
||||||
|
import scala.util.Success
|
||||||
|
import scala.util.Failure
|
||||||
|
import cats.effect.Effect
|
||||||
|
import play.api.mvc._
|
||||||
|
import scala.concurrent.ExecutionContext
|
||||||
|
|
||||||
|
object IOHttp {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actions to convert an effect wrapper F such as cats IO
|
||||||
|
* or monix Task into a future implicitly
|
||||||
|
*
|
||||||
|
* @param ab
|
||||||
|
*/
|
||||||
|
implicit class ActionBuilderOps[+R[_], B](ab: ActionBuilder[R, B]) {
|
||||||
|
|
||||||
|
import cats.effect.implicits._
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action to convert an effect wrapper F such as cats IO
|
||||||
|
* or monix Task into a future implicitly
|
||||||
|
*
|
||||||
|
* @param ab
|
||||||
|
*/
|
||||||
|
def asyncFR[F[_]: Effect](cb: R[B] => F[Result]): Action[B] = ab.async {
|
||||||
|
c => cb(c).toIO.unsafeToFuture()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action to convert an effect wrapper F such as cats IO
|
||||||
|
* or monix Task into a future implicitly
|
||||||
|
*
|
||||||
|
* @param ab
|
||||||
|
*/
|
||||||
|
def asyncF[F[_]: Effect](cb: => F[Result]): Action[AnyContent] =
|
||||||
|
ab.async { cb.toIO.unsafeToFuture() }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object RepoUtil {
|
||||||
|
def fromFuture[IOEffect[A], A](
|
||||||
|
f: => Future[A]
|
||||||
|
)(implicit ec: ExecutionContext): IO[A] =
|
||||||
|
IO.delay(f) >>= (f =>
|
||||||
|
IO.async[A] { cb =>
|
||||||
|
f.onComplete {
|
||||||
|
case Success(a) => cb(Right(a))
|
||||||
|
case Failure(ex) => cb(Left(ex))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
32
build.sbt
32
build.sbt
@ -20,9 +20,8 @@ resolvers in ThisBuild += Resolver.sonatypeRepo("snapshots")
|
|||||||
|
|
||||||
libraryDependencies in ThisBuild ++= Seq(
|
libraryDependencies in ThisBuild ++= Seq(
|
||||||
"javax.inject" % "javax.inject" % "1",
|
"javax.inject" % "javax.inject" % "1",
|
||||||
// "joda-time" % "joda-time" % "2.10.2",
|
"com.google.inject" % "guice" % "4.2.3",
|
||||||
// "org.joda" % "joda-convert" % "2.2.1",
|
"org.flywaydb" %% "flyway-play" % "6.0.0"
|
||||||
"com.google.inject" % "guice" % "4.2.3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
scalaVersion in ThisBuild := "2.13.1"
|
scalaVersion in ThisBuild := "2.13.1"
|
||||||
@ -35,13 +34,13 @@ scalacOptions in ThisBuild ++= Seq(
|
|||||||
"-Xlint",
|
"-Xlint",
|
||||||
"-Ywarn-numeric-widen"
|
"-Ywarn-numeric-widen"
|
||||||
)
|
)
|
||||||
javacOptions in ThisBuild ++= Seq("-source", "1.8", "-target", "1.8")
|
javacOptions in ThisBuild ++= Seq("-source", "11", "-target", "11")
|
||||||
|
|
||||||
lazy val flyway = (project in file("modules/flyway"))
|
lazy val flyway = (project in file("modules/flyway"))
|
||||||
.enablePlugins(FlywayPlugin)
|
.enablePlugins(FlywayPlugin)
|
||||||
.settings(
|
.settings(
|
||||||
libraryDependencies += "org.flywaydb" % "flyway-core" % FlywayVersion,
|
libraryDependencies += "org.flywaydb" % "flyway-core" % FlywayVersion,
|
||||||
flywayLocations := Seq("classpath:db/migration"),
|
flywayLocations := Seq("classpath:conf/db/migration/default"),
|
||||||
flywayUrl := databaseUrl,
|
flywayUrl := databaseUrl,
|
||||||
flywayUser := databaseUser,
|
flywayUser := databaseUser,
|
||||||
flywayPassword := databasePassword,
|
flywayPassword := databasePassword,
|
||||||
@ -50,8 +49,13 @@ lazy val flyway = (project in file("modules/flyway"))
|
|||||||
|
|
||||||
lazy val api = (project in file("modules/api")).settings(
|
lazy val api = (project in file("modules/api")).settings(
|
||||||
libraryDependencies ++= Seq(
|
libraryDependencies ++= Seq(
|
||||||
"com.typesafe.play" %% "play-json" % "2.8.1",
|
"com.typesafe.play" %% "play-json" % "2.8.1",
|
||||||
),
|
"org.typelevel" %% "cats-core" % "2.1.1",
|
||||||
|
"org.typelevel" %% "cats-effect" % "2.1.4",
|
||||||
|
"io.monix" %% "monix" % "3.2.2",
|
||||||
|
"com.typesafe.scala-logging" %% "scala-logging" % "3.9.2",
|
||||||
|
"ch.qos.logback" % "logback-classic" % "1.2.3"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
lazy val slick = (project in file("modules/slick"))
|
lazy val slick = (project in file("modules/slick"))
|
||||||
@ -62,8 +66,12 @@ lazy val slick = (project in file("modules/slick"))
|
|||||||
"com.typesafe.slick" %% "slick" % "3.3.2",
|
"com.typesafe.slick" %% "slick" % "3.3.2",
|
||||||
"com.typesafe.slick" %% "slick-hikaricp" % "3.3.2",
|
"com.typesafe.slick" %% "slick-hikaricp" % "3.3.2",
|
||||||
"io.bfil" %% "automapper" % "0.7.0",
|
"io.bfil" %% "automapper" % "0.7.0",
|
||||||
"io.scalaland" %% "chimney" % "0.5.2"
|
"io.scalaland" %% "chimney" % "0.5.2",
|
||||||
// "com.github.tototoshi" %% "slick-joda-mapper" % "2.4.1"
|
"org.typelevel" %% "cats-core" % "2.1.1",
|
||||||
|
"org.typelevel" %% "cats-effect" % "2.1.4",
|
||||||
|
"io.monix" %% "monix" % "3.2.2",
|
||||||
|
"com.typesafe.scala-logging" %% "scala-logging" % "3.9.2",
|
||||||
|
"ch.qos.logback" % "logback-classic" % "1.2.3"
|
||||||
),
|
),
|
||||||
slickCodegenDatabaseUrl := databaseUrl,
|
slickCodegenDatabaseUrl := databaseUrl,
|
||||||
slickCodegenDatabaseUser := databaseUser,
|
slickCodegenDatabaseUser := databaseUser,
|
||||||
@ -74,9 +82,6 @@ lazy val slick = (project in file("modules/slick"))
|
|||||||
slickCodegenExcludedTables := Seq("schema_version"),
|
slickCodegenExcludedTables := Seq("schema_version"),
|
||||||
slickCodegenCodeGenerator := { (model: m.Model) =>
|
slickCodegenCodeGenerator := { (model: m.Model) =>
|
||||||
new SourceCodeGenerator(model) {
|
new SourceCodeGenerator(model) {
|
||||||
// override def code =
|
|
||||||
// "import com.github.tototoshi.slick.H2JodaSupport._\n" + "import org.joda.time.DateTime\n" + super.code
|
|
||||||
|
|
||||||
override def Table = new Table(_) {
|
override def Table = new Table(_) {
|
||||||
override def Column = new Column(_) {
|
override def Column = new Column(_) {
|
||||||
override def rawType = model.tpe match {
|
override def rawType = model.tpe match {
|
||||||
@ -104,7 +109,6 @@ lazy val root = (project in file("."))
|
|||||||
"com.h2database" % "h2" % "1.4.199",
|
"com.h2database" % "h2" % "1.4.199",
|
||||||
ws % Test,
|
ws % Test,
|
||||||
"org.flywaydb" % "flyway-core" % FlywayVersion % Test,
|
"org.flywaydb" % "flyway-core" % FlywayVersion % Test,
|
||||||
"com.typesafe.play" %% "play-slick" % "5.0.0",
|
|
||||||
"org.scalatestplus.play" %% "scalatestplus-play" % "5.0.0" % Test,
|
"org.scalatestplus.play" %% "scalatestplus-play" % "5.0.0" % Test,
|
||||||
"com.typesafe.play" %% "play-json" % "2.8.1",
|
"com.typesafe.play" %% "play-json" % "2.8.1",
|
||||||
"io.bfil" %% "automapper" % "0.7.0"
|
"io.bfil" %% "automapper" % "0.7.0"
|
||||||
@ -113,4 +117,4 @@ lazy val root = (project in file("."))
|
|||||||
)
|
)
|
||||||
.aggregate(slick)
|
.aggregate(slick)
|
||||||
.dependsOn(slick)
|
.dependsOn(slick)
|
||||||
// fork := true
|
.dependsOn(flyway)
|
||||||
|
@ -5,4 +5,18 @@ myapp = {
|
|||||||
numThreads=20
|
numThreads=20
|
||||||
maxConnections=20
|
maxConnections=20
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
play.filters.enabled += "play.filters.cors.CORSFilter"
|
||||||
|
play.modules.enabled += "org.flywaydb.play.PlayModule"
|
||||||
|
|
||||||
|
db.default.driver=${myapp.database.driver}
|
||||||
|
db.default.url=${myapp.database.url}
|
||||||
|
db.default.username=${myapp.database.user}
|
||||||
|
db.default.password=${myapp.database.password}
|
||||||
|
|
||||||
|
play.filters.cors {
|
||||||
|
allowedHttpMethods = ["GET", "POST", "OPTION"]
|
||||||
|
allowedHttpHeaders = ["Accept"]
|
||||||
|
preflightMaxAge = 3 days
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
<configuration>
|
<configuration>
|
||||||
|
|
||||||
<conversionRule conversionWord="coloredLevel" converterClass="play.api.libs.logback.ColoredLevel"/>
|
<conversionRule conversionWord="coloredLevel" converterClass="play.api.libs.logback.ColoredLevel" />
|
||||||
|
|
||||||
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
|
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
|
||||||
<file>${application.home:-.}/logs/application.log</file>
|
<file>${application.home:-.}/logs/application.log</file>
|
||||||
@ -11,29 +11,31 @@
|
|||||||
|
|
||||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
<encoder>
|
<encoder>
|
||||||
<pattern>%coloredLevel %logger{15} - %message%n%xException{10}</pattern>
|
<pattern>%date{HH:mm:ss.SSS} %thread %coloredLevel %logger{15} - %message%n%xException{10}</pattern>
|
||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
<appender name="ASYNCFILE" class="ch.qos.logback.classic.AsyncAppender">
|
<appender name="ASYNCFILE" class="ch.qos.logback.classic.AsyncAppender">
|
||||||
<appender-ref ref="FILE"/>
|
<appender-ref ref="FILE" />
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
<appender name="ASYNCSTDOUT" class="ch.qos.logback.classic.AsyncAppender">
|
<appender name="ASYNCSTDOUT" class="ch.qos.logback.classic.AsyncAppender">
|
||||||
<appender-ref ref="STDOUT"/>
|
<appender-ref ref="STDOUT" />
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
<logger name="play" level="INFO"/>
|
<logger name="play" level="DEBUG" />
|
||||||
<logger name="application" level="INFO"/>
|
<logger name="application" level="DEBUG" />
|
||||||
|
|
||||||
<!-- Useful debugging settings in slick -->
|
<!-- Useful debugging settings in slick -->
|
||||||
<logger name="slick.jdbc.JdbcBackend.statement" level="INFO"/>
|
<logger name="slick.jdbc.JdbcBackend.statement" level="INFO" />
|
||||||
<logger name="slick.jdbc.JdbcBackend.benchmark" level="INFO"/>
|
<logger name="slick.jdbc.JdbcBackend.benchmark" level="INFO" />
|
||||||
<logger name="com.zaxxer.hikari" level="WARN"/>
|
<logger name="com.zaxxer.hikari" level="INFO" />
|
||||||
|
|
||||||
<root level="WARN">
|
<logger name="controllers" level="DEBUG" />
|
||||||
<appender-ref ref="ASYNCFILE"/>
|
|
||||||
<appender-ref ref="ASYNCSTDOUT"/>
|
<root level="INFO">
|
||||||
|
<appender-ref ref="ASYNCFILE" />
|
||||||
|
<appender-ref ref="ASYNCSTDOUT" />
|
||||||
</root>
|
</root>
|
||||||
|
|
||||||
</configuration>
|
</configuration>
|
||||||
|
@ -6,7 +6,8 @@
|
|||||||
GET / controllers.HomeController.index
|
GET / controllers.HomeController.index
|
||||||
GET /cars controllers.HomeController.cars
|
GET /cars controllers.HomeController.cars
|
||||||
GET /book controllers.HomeController.book
|
GET /book controllers.HomeController.book
|
||||||
GET /authors/:bookId controllers.HomeController.authors(bookId: Long)
|
GET /authors/:bookId controllers.MonixHomeController.authors(bookId: Long)
|
||||||
|
GET /user controllers.HomeController.user
|
||||||
|
|
||||||
# Map static resources from the /public folder to the /assets URL path
|
# Map static resources from the /public folder to the /assets URL path
|
||||||
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
|
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
package com.example.services
|
||||||
|
|
||||||
|
import monix.eval.Task
|
||||||
|
import com.example.models.Book
|
||||||
|
import com.example.models.Author
|
||||||
|
import com.example.models.NewAuthor
|
||||||
|
import com.example.models.NewBook
|
||||||
|
|
||||||
|
trait TaskLibraryService {
|
||||||
|
// Simple function that returns a book
|
||||||
|
def findBookById(id: Long): Task[Option[Book]]
|
||||||
|
|
||||||
|
// Simple function that returns a list of books with it's author
|
||||||
|
def findBooksWithAuthor: Task[Seq[(Book, Author)]]
|
||||||
|
|
||||||
|
// Insert a book and an author composing two DBIOs in a transaction
|
||||||
|
def insertBookAndAuthor(book: NewBook, author: NewAuthor): Task[(Long, Long)]
|
||||||
|
|
||||||
|
def getAuthorsForBook(id: Long): Task[Seq[(Author, Book)]]
|
||||||
|
}
|
@ -23,7 +23,8 @@ myapp = {
|
|||||||
connectionTimeout = 5000
|
connectionTimeout = 5000
|
||||||
validationTimeout = 5000
|
validationTimeout = 5000
|
||||||
|
|
||||||
connectionPool=disabled
|
connectionPool = disabled
|
||||||
|
keepAlive = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,13 +5,11 @@ import com.example.models._
|
|||||||
import io.scalaland.chimney.dsl._
|
import io.scalaland.chimney.dsl._
|
||||||
import com.example.user.slick.Tables
|
import com.example.user.slick.Tables
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
// import slick.jdbc.H2Profile.api._
|
import javax.inject._
|
||||||
// import scala.concurrent.ExecutionContext
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class SlickLibraryDbio extends Tables {
|
class SlickLibraryDbio @Inject() (override val profile: JdbcProfile)
|
||||||
|
extends Tables {
|
||||||
override val profile: JdbcProfile = _root_.slick.jdbc.H2Profile
|
|
||||||
|
|
||||||
import profile.api._
|
import profile.api._
|
||||||
|
|
||||||
@ -110,12 +108,12 @@ class SlickLibraryDbio extends Tables {
|
|||||||
}
|
}
|
||||||
} yield (author, book)
|
} yield (author, book)
|
||||||
|
|
||||||
lazy val authorOfBook4 = (bookId: Long) =>
|
lazy val authorOfBook4 = (bookId: Long) =>
|
||||||
for {
|
for {
|
||||||
book <- Books
|
book <- Books
|
||||||
author <- book.authorsFk
|
author <- book.authorsFk
|
||||||
} yield (author, book)
|
} yield (author, book)
|
||||||
|
|
||||||
}
|
}
|
||||||
case class BookWithoutId(title: String, authorId: Long)
|
case class BookWithoutId(title: String, authorId: Long)
|
||||||
// def test() = {
|
// def test() = {
|
||||||
|
@ -16,18 +16,19 @@ class SlickLibraryService @Inject() (
|
|||||||
libraryDbio: SlickLibraryDbio
|
libraryDbio: SlickLibraryDbio
|
||||||
)(implicit ec: ExecutionContext)
|
)(implicit ec: ExecutionContext)
|
||||||
extends LibraryService {
|
extends LibraryService {
|
||||||
|
import libraryDbio.profile.api._
|
||||||
|
|
||||||
override def getAuthorsForBook(id: Long): Future[Seq[(Author, Book)]] = {
|
override def getAuthorsForBook(id: Long): Future[Seq[(Author, Book)]] = {
|
||||||
db.run{
|
db.run {
|
||||||
libraryDbio.getAuthorsForBook(id).map(_.map {
|
libraryDbio
|
||||||
|
.getAuthorsForBook(id)
|
||||||
|
.map(_.map {
|
||||||
case (x, y) => {
|
case (x, y) => {
|
||||||
( libraryDbio.authorsRowToAuthor(x), libraryDbio.booksRowToBooks(y))
|
(libraryDbio.authorsRowToAuthor(x), libraryDbio.booksRowToBooks(y))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
import libraryDbio.profile.api._
|
|
||||||
|
|
||||||
// Simple function that returns a book
|
// Simple function that returns a book
|
||||||
def findBookById(id: Long): Future[Option[Book]] =
|
def findBookById(id: Long): Future[Option[Book]] =
|
||||||
|
@ -0,0 +1,97 @@
|
|||||||
|
package com.example.user.slick.services
|
||||||
|
|
||||||
|
import javax.inject._
|
||||||
|
// import slick.jdbc.JdbcProfile
|
||||||
|
import slick.jdbc.JdbcBackend.Database
|
||||||
|
import com.example.models._
|
||||||
|
import com.example.user.slick.dbios.SlickLibraryDbio
|
||||||
|
import monix.eval.Task
|
||||||
|
import com.example.services.TaskLibraryService
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class TaskSlickLibraryService @Inject() (
|
||||||
|
db: Database,
|
||||||
|
libraryDbio: SlickLibraryDbio
|
||||||
|
) extends TaskLibraryService {
|
||||||
|
import libraryDbio.profile.api._
|
||||||
|
|
||||||
|
override def findBookById(id: Long): Task[Option[Book]] =
|
||||||
|
Task
|
||||||
|
.deferFuture {
|
||||||
|
db.run(
|
||||||
|
libraryDbio.findBookById(id)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.flatMap(record => Task.eval(record.map(libraryDbio.booksRowToBooks)))
|
||||||
|
|
||||||
|
override def findBooksWithAuthor: Task[Seq[(Book, Author)]] =
|
||||||
|
Task.deferFuture(db.run(libraryDbio.findBooksWithAuthor)).flatMap {
|
||||||
|
records =>
|
||||||
|
Task.traverse(records) {
|
||||||
|
case (x, y) =>
|
||||||
|
Task.eval {
|
||||||
|
(
|
||||||
|
libraryDbio.booksRowToBooks(x),
|
||||||
|
libraryDbio.authorsRowToAuthor(y)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// override def storeMulti(aggregates: Seq[AggregateType]): Task[Long] =
|
||||||
|
// for {
|
||||||
|
// records <- Task.traverse(aggregates) { aggregate =>
|
||||||
|
// convertToRecord(aggregate)
|
||||||
|
// }
|
||||||
|
// result <- Task.deferFutureAction { implicit ec =>
|
||||||
|
// import profile.api._
|
||||||
|
// db.run(DBIO.sequence(records.foldLeft(Seq.empty[DBIO[Long]]) {
|
||||||
|
// case (result, record) =>
|
||||||
|
// result :+ dao.insertOrUpdate(record).map(_.toLong)
|
||||||
|
// }))
|
||||||
|
// .map(_.sum)
|
||||||
|
// }
|
||||||
|
// } yield result
|
||||||
|
|
||||||
|
override def insertBookAndAuthor(
|
||||||
|
book: NewBook,
|
||||||
|
author: NewAuthor
|
||||||
|
): Task[(Long, Long)] =
|
||||||
|
Task.deferFutureAction { implicit scheduler =>
|
||||||
|
val action = for {
|
||||||
|
authorId <- libraryDbio.insertAuthor2(author)
|
||||||
|
bookId <- libraryDbio.insertBook2(book.copy(authorId = authorId))
|
||||||
|
} yield (bookId, authorId)
|
||||||
|
|
||||||
|
db.run(action.transactionally)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def getAuthorsForBook(id: Long): Task[Seq[(Author, Book)]] =
|
||||||
|
for {
|
||||||
|
records <- Task.deferFuture(db.run(libraryDbio.getAuthorsForBook((id))))
|
||||||
|
result <- Task.traverse(records) {
|
||||||
|
case (authorsRow, booksRow) =>
|
||||||
|
Task.eval(
|
||||||
|
(
|
||||||
|
libraryDbio.authorsRowToAuthor(authorsRow),
|
||||||
|
libraryDbio.booksRowToBooks(booksRow)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} yield (result)
|
||||||
|
|
||||||
|
// Task.deferFutureAction { implicit scheduler =>
|
||||||
|
// db.run(
|
||||||
|
// libraryDbio
|
||||||
|
// .getAuthorsForBook(id)
|
||||||
|
// .map(_.map {
|
||||||
|
// case (x, y) => {
|
||||||
|
// (
|
||||||
|
// libraryDbio.authorsRowToAuthor(x),
|
||||||
|
// libraryDbio.booksRowToBooks(y)
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user