当 IO 卡在中间时如何编写 doobie 事务

How to write a doobie transaction when there's an IO stuck in the middle

我想写一个基本的 doobie read/write 交易,但更重要的是中间有一个 IO 返回调用。我想做这样的事情:

abstract class MyDAO {

  def readSomething(id: String): ConnectionIO[Option[Something]]

  def writeSomething(something: Something): ConnectionIO[Unit]

}

class MyService {

  def getNewSomething: IO[Something] = ???

}

class MyProgram(myDAO: MyDAO, myService: MyService, xa: DataSourceTransactor[IO]) {

  val transaction = myDAO.readSomething("xyz").flatMap {
    case Some(thing) => IO.pure(thing).pure[ConnectionIO] //ConnectionIO[IO[Something]]
    case None => myService.getNewSomething.map { newSomething =>
      myDAO.writeSomething(newSomething).map(_ => newSomething)
    }.sequence[ConnectionIO, Something] //can't sequence an IO! So I'm stuck with IO[ConnectionIO[Something]]
  }

  transaction.transact(xa)

}

但我无法在 IO 上排序。因此,在第二种情况下,我会被 IO[ConnectionIO[Something]] 困住,这意味着我的交易最终会像 ConnectionIO[IO[ConnectionIO[Something]] 一样。

我想要的是一个 ConnectionIO[IO[Something]],然后我可以在单个事务中 运行 产生 IO[IO[Something]] 然后我可以轻松地压平它。 (我不想运行 IO。)有道理吗?知道这是否有可能实现吗?

理论上您可以使用 cats-effect 提供并由 doobie 实现的 LiftIO 类型类,如下所示:

import cats.effect._
import doobie._
import doobie.implicits._

def read: ConnectionIO[Int] = ???
def write(s: String): ConnectionIO[Unit] = ???
def transform(i: Int): IO[String] = ???

val transaction: ConnectionIO[Unit] = for {
  i <- read
  s <- transform(i).to[ConnectionIO]
  _ <- write(s)
} yield ()

transaction.transact(xa)

注意 to[ConnectionIO]。它需要一个 LiftIO 类型的隐式参数,它看起来像这样并且几乎可以做你想做的事情(将 IO 提升为 F):

trait LiftIO[F[_]] {
  def liftIO[A](ioa: IO[A]): F[A]
}