I am currently playing with Arrow.io in Kotlin and I would love to use the library (together with Spring Boot) in a project at work. One problem I don't quite know how to solve correctly is transaction management. IO<A>.bracketCase(...)
seems to me to be the correct tool for the job. Here is the approach I'm currently working on:
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()
}
Sadly, it doesn't work as I expect it to: when an exception is thrown during execution of action
, I would expect a rollback. But this is not the case (the following test fails for instance):
@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()
}
I played around with this a lot now and no matter how I arrange my types and where I place the action
in question - I never get bracketCase
to roll back my transactions. What am I doing wrong? Is there a better approach to do this? I would prefer a typesafe way without using unsafeRun
if that is at all possible.