Состав Doobie и доступа к БД за 1 транзакцию
Книга Doobie говорит, что это хорошая практика, чтобы вернуть ConnectionIO из вашего уровня хранилища. Это дает возможность связывать звонки и выполнять их за одну транзакцию. Красиво и понятно.
Теперь давайте представим, что мы работаем над сервисом REST API, и наш сценарий таков:
- Найти объект в базе данных
- Выполните некоторые асинхронные манипуляции (используя cats.effect.IO или monix.eval.Task) с этим объектом.
- Сохраните объект в базе данных.
И мы хотим выполнить все эти шаги внутри 1 транзакции. Проблема в том, что без естественной трансформации, которая дается нам transactor.trans()
мы работаем внутри 2 монады - Task
а также ConnectionIO
, Это невозможно.
Вопрос в том - как правильно смешать дуби ConnectionIO
с какой-либо эффектовой монадой в 1 композиции, такой как мы работаем в 1 транзакции и можем зафиксировать / откатить все мутации БД в конце света?
Спасибо!
UPD: маленький пример
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: правильный ответ, предоставленный @oleg-pyzhcov, состоит в том, чтобы поднять ваши типы данных эффекта в ConnectionIO
как это:
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)
1 ответ
ConnectionIO
в Doobie есть cats.effect.Async
экземпляр, который, помимо прочего, позволяет вам превратить любой cats.effect.IO
в ConnectionIO
посредством liftIO
метод:
import doobie.free.connection._
import cats.effect.{IO, Async}
val catsIO: IO[String] = ???
val cio: ConnectionIO[String] = Async[ConnectionIO].liftIO(catsIO)
За monix.eval.Task
Ваша лучшая ставка использует Task#toIO
и выполнить тот же трюк, но вам понадобится моникс Scheduler
в рамках.