Browse Source
Many changes
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 debugmaster
Rohan Sircar
4 years ago
22 changed files with 353 additions and 63 deletions
-
31app/Module.scala
-
25app/controllers/HomeController.scala
-
34app/controllers/MonixHomeController.scala
-
28app/services/MyService.scala
-
18app/util/Schedulers.scala
-
58app/util/Util.scala
-
32build.sbt
-
14conf/application.conf
-
26conf/logback.xml
-
3conf/routes
-
20modules/api/src/main/scala/com/example/services/TaskLibraryService.scala
-
0modules/flyway/src/main/resources/db/migration/default/V1__create_users_table.sql
-
0modules/flyway/src/main/resources/db/migration/default/V2__add_user.sql
-
0modules/flyway/src/main/resources/db/migration/default/V3__create_cars_table.sql
-
0modules/flyway/src/main/resources/db/migration/default/V4__add_car.sql
-
0modules/flyway/src/main/resources/db/migration/default/V5__authors_books_table.sql
-
0modules/flyway/src/main/resources/db/migration/default/V6__insert_books_and_authors.sql
-
0modules/flyway/src/main/resources/db/migration/default/V7__test.sql
-
3modules/slick/src/main/resources/application.conf
-
12modules/slick/src/main/scala/com/example/user/slick/dbios/SlickLibraryDbio.scala
-
15modules/slick/src/main/scala/com/example/user/slick/services/SlickLibraryService.scala
-
97modules/slick/src/main/scala/com/example/user/slick/services/TaskSlickLibraryService.scala
@ -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 play.api.db.slick.DatabaseConfigProvider |
|||
import scala.concurrent.ExecutionContext |
|||
import play.api.db.slick.HasDatabaseConfigProvider |
|||
import slick.jdbc.JdbcProfile |
|||
// import javax.inject._ |
|||
// import play.api.db.slick.DatabaseConfigProvider |
|||
// import scala.concurrent.ExecutionContext |
|||
// import play.api.db.slick.HasDatabaseConfigProvider |
|||
// import slick.jdbc.JdbcProfile |
|||
|
|||
@Singleton |
|||
class MyService @Inject() ( |
|||
protected val dbConfigProvider: DatabaseConfigProvider, |
|||
implicit val ec: ExecutionContext |
|||
) extends HasDatabaseConfigProvider[JdbcProfile] { |
|||
// @Singleton |
|||
// class MyService @Inject() ( |
|||
// protected val dbConfigProvider: DatabaseConfigProvider, |
|||
// implicit val ec: ExecutionContext |
|||
// ) extends HasDatabaseConfigProvider[JdbcProfile] { |
|||
|
|||
import profile.api._ |
|||
db |
|||
} |
|||
// import profile.api._ |
|||
// db |
|||
// } |
@ -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 |
@ -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)) |
|||
} |
|||
} |
|||
) |
|||
} |
@ -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)]] |
|||
} |
@ -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) |
|||
// ) |
|||
// } |
|||
// }) |
|||
// ) |
|||
// } |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue