Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Doobie and DB access composition within 1 transaction

Doobie book says that it's a good practice to return ConnectionIO from your repository layer. It gives an ability to chain calls and perform them in one transaction. Nice and clear.

Now let's imagine we are working on REST API service and our scenario is:

  1. Find an object in database
  2. Perform some async manipulation (using cats.effect.IO or monix.eval.Task) with this object.
  3. Store the object in database.

And we want to perform all these steps inside 1 transaction. The problem is that without natural transformation which is given for us by transactor.trans() we are working inside 2 monads - Task and ConnectionIO. That's not possible.

The question is - how to mix doobie ConnectionIO with any effect monad in 1 composition such as we are working in 1 transaction and able to commit/rollback all DB mutations at the end of the world?

Thank you!

UPD: small example

def getObject: ConnectionIO[Request]                      = ???
def saveObject(obj: Request): ConnectionIO[Request]       = ???
def processObject(obj: Request): monix.eval.Task[Request] = ???

val transaction:??? = for {
    obj       <- getObject             //ConnectionIO[Request]
    processed <- processObject(obj)    //monix.eval.Task[Request]
    updated   <- saveObject(processed) //ConnectionIO[Request]
  } yield updated

UPD2: The correct answer provided by @oleg-pyzhcov is to lift your effect datatypes to ConnectionIO like this:

def getObject: ConnectionIO[Request]                      = ???
def saveObject(obj: Request): ConnectionIO[Request]       = ???
def processObject(obj: Request): monix.eval.Task[Request] = ???

val transaction: ConnectionIO[Request] = for {
    obj       <- getObject                                           //ConnectionIO[Request]
    processed <- Async[ConnectionIO].liftIO(processObject(obj).toIO) //ConnectionIO[Request]
    updated   <- saveObject(processed)                               //ConnectionIO[Request]
} yield updated
val result: Task[Request] = transaction.transact(xa)
like image 723
Eugene Zhulkov Avatar asked May 22 '18 16:05

Eugene Zhulkov


1 Answers

ConnectionIO in doobie has a cats.effect.Async instance, which, among other things, allows you do turn any cats.effect.IO into ConnectionIO by means of liftIO method:

import doobie.free.connection._
import cats.effect.{IO, Async}
val catsIO: IO[String] = ???
val cio: ConnectionIO[String] = Async[ConnectionIO].liftIO(catsIO)

For monix.eval.Task, your best bet is using Task#toIO and performing the same trick, but you'd need a monix Scheduler in scope.

like image 133
Oleg Pyzhcov Avatar answered Nov 03 '22 14:11

Oleg Pyzhcov