Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Slick monadic Actions
package slick
/**
* This is some code extracted from TimeOut codebase, demonstrating:
* - Use of tag typed to avoid mixing session of different DB
* - The use of the Reader Monad to compose Actions together and defer the choice of async/sync computation
*
* I remove the part where we can say if our operation are read only or not (to use different connection), in order to
* make things easier.
**/
// A simple Reader Monad
class Reader[E, A](val f: E => A) extends AnyVal {
def run(e: E): A = f(e)
def map[B](f: A => B) = Reader[E, B](e => f(run(e)))
def mapEnv[B](f: B => E): Reader[B, A] = Reader[B, A](b => run(f(b)))
def flatMap[B](f: A => Reader[E, B]) = Reader[E, B](e => f(run(e)).run(e))
}
object Reader {
def apply[E, A](f: E => A): Reader[E, A] = new Reader[E, A](f)
def inject[E, A](a: A): Reader[E, A] = new Reader[E, A](_ => a)
}
// We instantiate a wrapper like this for each DB, it's a way to pimp slick feature and being in the cake
abstract class SlickDb[S <: Schema](name: String, schema: S)(implicit executionContext: ExecutionContext) extends Disposable {
val db: Database = Database.forDataSource(source)
type Action[T] = Reader[Session, T]
type Session = slick.session.Session @@ this.type
object Action {
def apply[T](f: Session => T): Action[T] = Reader[Session, T](f)
def inject[T](x: T): Action[T] = Reader[Session, T](_ => x)
}
// This is not ideal, as the you need to import db._ to get in scope, but that do the job
implicit class RichAction[T](action: Action[T]) {
def future = asyncTx(action.run)
def get = readTx(action.run)
}
def run[T](action: Action[T]) = action.get
def asyncTx[A](f: Session => A): Future[A] = Future(writeDb.withTransaction(f))
def session(session: slick.session.Session) = tag[this.type](session)
def tx[A](f: Session => A): A = writeDb.withTransaction(f)
}
/** USAGE example **/
class ContentService(db: SlickDb[...] {
import db._
def findTranslation(contentId: Int): Action[Translation] =
Action { implicit session => Query(...) }
def findContent(contentId: Int): Action[Content] = ...
// Then you can ...
// In same transaction, and then get a future
(for {
c <- findContent(0)
t <- findTranslation(0)
} yield c -> t).future
// Done in parallel
(for {
c <- findContent(0).future
t <- findTranslation(0).future
} yield c -> t)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.