Книга Дуби говорит, что рекомендуется возвращать ConnectionIO из уровня хранилища.Это дает возможность связывать звонки и выполнять их за одну транзакцию.Красиво и понятно.
Теперь давайте представим, что мы работаем над службой REST API, и наш сценарий:
- Найти объект в базе данных
- Выполнить некоторые асинхронные манипуляции (используя cats.effect.IO или monix.eval.Task) с этим объектом.
- Сохранение объекта в базе данных.
И мы хотим выполнить все эти шаги в одной транзакции.Проблема в том, что без естественной трансформации, которую нам дает transactor.trans()
, мы работаем внутри 2 монад - Task
и ConnectionIO
.Это невозможно.
Вопрос в том, как смешать doobie 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)