Kotlin 的 Arrow <Exception, X> 和交易

问题描述

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

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

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

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

提前致谢

解决方法

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

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

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

如果您更喜欢使用 Either API,您可能可以使用 Either<RuntimeException,A>Either<E,A> 将 Spring 基于异常的 API 包装起来。

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

  1. 如果是这样,您如何通过它们实现回滚?

transaction: () -> A 包装 transactionEither: () -> Either<E,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<E,A>,同时保持 Spring 基于异常的模型完好无损。

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

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

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

  • 使用 Either#flatMapeither { } 链接相关值 (Either<E,A>) + (A) -> Either<E,B>
  • 使用 Either#zip 组合独立值。 Either<E,A> + Either<E,B> + (A,B) -> C
,

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

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

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

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

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.我把这个挤进去,你如何避免嵌套两个?

您可以使用 Either.fx { } 来避免嵌套。注意!将任何值绑定到 .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 等功能链接。