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:
- Find an object in database
- Perform some async manipulation (using cats.effect.IO or monix.eval.Task) with this object.
- 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)