Kotlin & Arrow.io: транзакции с IO.bracketCase
В настоящее время я играю с Arrow.io в Котлине, и я хотел бы использовать библиотеку (вместе с Spring Boot) в проекте на работе. Одна проблема, которую я не совсем знаю, как правильно решить, это управление транзакциями. IO<A>.bracketCase(...)
Мне кажется, это правильный инструмент для работы. Вот подход, над которым я сейчас работаю:
interface TransactionProvider {
fun startTransaction(): IO<Transaction>
}
interface Transaction {
fun commit(): IO<Unit>
fun rollback(): IO<Unit>
}
fun <A> TransactionProvider.runInTransaction(action: IO<A>): IO<A> =
startTransaction()
.bracketCase(::releaseTransaction) { action }
fun releaseTransaction(t: Transaction, exitCase: ExitCase<Throwable>): IO<Unit> =
when (exitCase) {
is ExitCase.Completed -> t.commit()
else -> t.rollback()
}
К сожалению, это не работает так, как я ожидаю: когда возникает исключение во время выполнения action
Я бы ожидал откат. Но это не так (например, следующий тест не пройден):
@Test
internal fun `transaction rolls back on failure`() {
val transaction: Transaction =
mock {
on { commit() } doReturn IO.unit
on { rollback() } doReturn IO.unit
}
val transactionProvider: TransactionProvider =
mock{
on { startTransaction() } doReturn IO.just(transaction)
}
val exception = IllegalArgumentException("Here I am!")
val action = IO{ throw exception }
val result: Either<Throwable, Unit> =
transactionProvider
.runInTransaction(action)
.attempt()
.unsafeRunSync()
assertThat(result).isEqualTo(exception.left())
verify(transaction, never()).commit()
verify(transaction, times(1)).rollback()
}
Я много играл с этим сейчас, и неважно, как я размещаю свои типы и где я размещаю action
под вопросом - я никогда не получаю bracketCase
откатить мои транзакции. Что я делаю неправильно? Есть ли лучший подход для этого? Я предпочел бы безопасный способ без использования unsafeRun
если это вообще возможно.
0 ответов
Думаю, у вас все в порядке, кроме тестового кода - возможно, макеты некорректно работают с IO
. Например, следующий тестовый код работает должным образом с использованием ваших определений транзакций в первом фрагменте:
fun main(args: Array<String>): Unit {
val transactor = object: Transaction {
override fun commit(): IO<Unit> = IO { println("Commit"); }
override fun rollback(): IO<Unit> = IO { println("Rollback"); }
}
val txProvider = object: TransactionProvider {
override fun startTransaction(): IO<Transaction> = IO.just(transactor)
}
val actionThrow = IO { println("Throwing"); throw IllegalArgumentException("Exception!") }
val actionSuccess = IO { println("Returning value"); 2 }
val resultThrow = txProvider.runInTransaction(actionThrow).attempt().unsafeRunSync()
println(resultThrow)
val resultSuccess = txProvider.runInTransaction(actionSuccess).attempt().unsafeRunSync()
println(resultSuccess)
}
и результат, который я получаю, выглядит следующим образом:
Throwing
Rollback
Left(a=java.lang.IllegalArgumentException: Exception!)
Returning value
Commit
Right(b=2)
это именно то, что я ожидал, используя #bracketCase
. (Используется Arrow 0.10.0-SNAPSHOT.)