Created
December 1, 2013 10:39
-
-
Save aloiscochard/7731519 to your computer and use it in GitHub Desktop.
Slick monadic Actions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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