Kotlin 的 Arrow Either<Exception, X> 和事务

Posted

技术标签:

【中文标题】Kotlin 的 Arrow Either<Exception, X> 和事务【英文标题】:Kotlin's Arrow Either<Exception, X> and transactions 【发布时间】:2021-07-21 17:42:24 【问题描述】:

我正在尝试使用 Kotlin 的 Arrow 库 Either 对象来处理项目中的异常。

到目前为止,我的经验还不错,但我正在努力寻找一种方法来处理与 Either 的事务 - 特别是回滚。在 Spring 中抛出 RuntimeException 是导致事务回滚的可靠方法。但是,使用Either 不会引发异常,因此不会触发回滚。

您可以将其视为一个多方面的问题:

    Either 是否适合真正的Exception 处理?不是替代控制流,我指的是程序流需要停止的真正错误情况。 如果是这样,您如何使用它们实现回滚? 如果问题 2. 的答案是以编程方式使用 transactionManager - 您能避免这种情况吗? 我把这个挤进去,如何避免嵌套Eithers?

【问题讨论】:

【参考方案1】:

您的一些问题没有直接的答案,但我会尽力而为:D

    是否适合真正的异常处理。不是替代控制流,我的意思是程序流需要停止的真正错误情况。

Spring 使用异常来模拟触发回滚,因此在这种情况下,您需要遵守 Spring 的机制。

如果您更喜欢使用Either API,您可以使用Either&lt;RuntimeException, A&gt;Either&lt;E, A&gt; 封装Spring 的基于异常的API。

所以要回答您的问题,Either 适合用于异常处理。但是,通常您只会捕获您感兴趣的异常并使用您自己的错误域对其进行建模。意外的异常,或者您无法解决的异常通常被允许冒泡。

    如果是这样,您如何使用它们实现回滚?

transactionEither: () -&gt; Either&lt;E, A&gt;包装transaction: () -&gt; A的伪代码示例。

class EitherTransactionException(val result: Either<Any?, Any?>): RuntimeException(..)

fun transactionEither(f: () -> Either<E, A>): Either<E, A> =
  try 
     val result = transaction  f() 
     when(val result) 
       is Either.Right -> result
       is Either.Left -> throw EitherTransactionException(result)
     
   catch(e: EitherTransactionException) 
     return e.result as Either<E, A>
  

现在您应该能够使用Either&lt;E, A&gt;,同时保持基于异常的 Spring 模型完好无损。

如果问题 2. 的答案是以编程方式使用 transactionManager - 你能避免这种情况吗?

我在回答问题 2 时已经回避了它。或者,通过以编程方式使用transactionManager,您可以避免抛出该异常并恢复该值。

我把这个挤进去,你如何避免嵌套 Eithers

使用Either#flatMapeither 链接依赖值(Either&lt;E, A&gt;) + (A) -&gt; Either&lt;E, B&gt; 使用Either#zip 组合独立值。 Either&lt;E, A&gt; + Either&lt;E, B&gt; + (A, B) -&gt; C

【讨论】:

【参考方案2】:

1.是否适合真正的异常处理?

一个有点自以为是的答案:通过异常进行的常规错误处理具有与使用可怕的 GOTO 语句类似的缺点:执行跳转到代码中的另一个特定点 - catch 语句。它比 GOTO 更好,因为它不会在完全任意点恢复执行,而是必须向上函数调用堆栈。

使用 Either 模型的错误处理,您不会中断程序流程,这使代码更易于推理、更易于调试、更易于测试。

因此,我会说它不仅合适,而且更好。

2。如果是这样,您如何使用它们实现回滚?

我建议使用 Springs 事务模板:示例:

fun <A, B> runInTransaction(block: () -> Either<A, B>): Either<A, B> 
  return transactionTemplate.execute 
    val result = block()
    return@execute when (result) 
      is Either.Left -> 
        it.setRollbackOnly()
        result
      
      is Either.Right -> result
   
!! // execute() is a java method which may return nulls

fun usage(): Either<String, String> 
  return runInTransaction 
    // do stuff
    return@runInTransaction "Some error".left()
  

现在这很简单,因为它将任何左侧值都视为需要回滚,您可能希望根据自己的目的对其进行调整,并使用密封类来封装您希望为左侧情况处理的可能错误结果.

您还需要为包含此方法的类提供一个 transactionTemplate。

3.如果问题 2. 的答案是以编程方式使用 transactionManager - 你能避免这种情况吗?

我不明白怎么做,因为 Springs 声明式事务管理是建立在异常错误处理模型上的,并且它会中断控制流。

4.我把这个挤进去,你如何避免嵌套 Eithers?

您可以使用Either.fx 来避免嵌套。注意!用于将 Either 值绑定到 .fx 范围的语法。可以这么说,这“解包”了它们:

fun example(): Either<Error, Unit> 
  return Either.fx 
            val accessToken: String = !getAccessToken()
            return@fx !callHttp(accessToken)
        


fun getAccessToken(): Either<Error, String> 
  return "accessToken".right()


fun callHttp(token: String): Either<Error, Unit>   
  return Unit.right()

为此,Left 值必须都是相同的类型。如果绑定了一个左值,它将被返回。这使您可以避免嵌套 when 语句或与 map/flatmap/fold 等的函数链接。

【讨论】:

以上是关于Kotlin 的 Arrow Either<Exception, X> 和事务的主要内容,如果未能解决你的问题,请参考以下文章

使用 Arrow 创建具有错误处理的对象构建器 - 模式匹配多个 Either

Kotlin Monad的学习

Kotlin 异常处理之 OptionEitherResult

Kotlin - 推断两个泛型参数之一的类型

如何绑定多个Either<,>?

在 Flutter 中解压 Option<Either<T,T>>