Scala中的交易效果


问题

Future可以将with的情况推广到某种效果F[_],因此我们可以说:我们如何F[_]以某种方式构成效果,如果其中一个失败,F[_]则将通过某个函数回滚先前的执行结果?此行为类似于我们称为“交易”的行为。

解决方案

对于以下cats-effect版本的代码解决方案2.3.1。最终的解决方案如下所示:

/**
 * Transactional effect provide possibility to recover effect execution result if it failed, but still return failed
 * result.
 *
 * @tparam F surrounding effect type
 */
class TransactionEffect[F[_], E](underlying: F[E], rollback: PartialFunction[Throwable, F[Unit]])
                                (implicit F: FlatMap[F], ME: MonadError[F, Throwable]) {
  /*
   * Here goes syntax trick - in for-comprehension will be invoked `flatMap` of this wrapper and not of underlying effect.
   */
  def flatMap[S](f: E => F[S]): F[S] = {
    F.flatMap(underlying)(f).recoverWith {
      case exception: Throwable =>
        val failure: F[S] = ME.raiseError[S](exception)
        rollback.lift(exception).fold(failure)(recoverEffect => F.flatMap(recoverEffect)(_ => failure))
    }
  }
}
object TransactionEffect {
  /**
   * Provides syntax sugar over [[TransactionEffect]] wrapper.
   */
  implicit class TransactionEffectSyntax[F[_], E](underling: F[E])
                                                 (implicit F: FlatMap[F], AE: MonadError[F, Throwable]){
    def rollbackWith(rollback: PartialFunction[Throwable, F[Unit]]): TransactionEffect[F, E] = {
      new TransactionEffect[F, E](underling, rollback)
    }
  }
}

和小演示:

object TransactionEffectDemo extends IOApp {
  import TransactionEffect._
  override def run(args: List[String]): IO[ExitCode] = {
    for {
      _ <- IO.delay(println("A executed")).rollbackWith {
        case _: Throwable => IO.delay(println("A recovered"))
      }
      _ <- IO.delay(println("B executed")).rollbackWith {
        case _: Throwable => IO.delay(println("B recovered"))
      }
      _ <- IO.raiseError(new Exception("C failed"))
    } yield ExitCode.Success
  }
}

它将打印出下一个输出(为便于说明,将其缩短):

A executed
B executed
B recovered
A recovered
java.lang.Exception: C failed

现实生活中的用法

如果应用程序是用纯函数式编程范式编写的,则理想情况下,它不应具有在其他效果失败的情况下需要“回滚”的任何副作用。但是,例如,当应用程序将文档存储在多个数据库(例如Mongo和Elasticsearch)中,并且如果在Elasticsearch中存储失败时,会从主MongoDB中删除文档时,此方法会很有用。

感谢您的关注,希望对您有所帮助!


原文链接:http://codingdict.com