1

Let's say I have a service method where I do some validation/restcalls etc. (e.g. someServiceMethod2 in) and want to make it safe in a transactional way. I also have a repoMethod which includes a transaction. How can I rollback the child transaction when the parent transaction throws an exception ?

Is there a way to join these two methods in a transaction? Just like what the TransactionDefinition.PROPAGATION_REQUIRED propagation would do in Spring lib.

fun someServiceMethod () {
    client.withTransaction { c ->
        val bla = someServiceMethod2() // works

        someRepo.doSthRepoStuff(bla)) // works

        throw Exception("Just for test purpose") // crashes -> should also rollback transaction from doSthRepoStuff
    }
}

...

fun doSthRepoStuff(bla : String) {
    client.withTransaction { c -> 
        // do db related stuff here
    }
}

The only way I could do it right now is to use only the service transaction and pass the connection to the repo method. This somehow feels weird to me (to give a repo method a sql connection)

Is there an elegant way to solve this ?

Ahmet K
  • 713
  • 18
  • 42

2 Answers2

1

Spent a lot of time to figure this out and the only way i was able to achieve this is by passing around the object of following class

io.vertx.reactivex.sqlclient.SqlConnection

And this worked pretty smooth.

Rahul
  • 31
  • 3
  • Like I mentioned in my op, I also thought about this option. It seems like it's the only way to do it. Now my services inject the DbClient and my repo classes only get the connection through the method args. So repos are not holding/injecting the pool at all. Feels a bit weird though – Ahmet K Dec 28 '21 at 12:27
0

You can use the withTransaction() function block with TransactionPropagation.CONTEXT parameter:

fun someServiceMethod() {
    client.withTransaction(TransactionPropagation.CONTEXT) { c ->
        val bla = someServiceMethod2() // works

        someRepo.doSthRepoStuff(bla)) // works

        throw Exception("Just for test purpose") // crashes -> should also rollback transaction from doSthRepoStuff
    }
}

...

fun doSthRepoStuff(bla : String) {
    client.withTransaction(TransactionPropagation.CONTEXT) { c -> 
        // do db related stuff here
    }
}

This way, if current context doesn't have a started transaction, it will be initiated and propagated to all subsequent DB calls, even in injected beans.

Just keep in mind, that all calls to the withTransaction() in all beans must have the TransactionPropagation.CONTEXT argument. To get a reference to a DB client, you just inject an instance of PgPool in each bean as usual:

@ApplicationScoped
class Bean1 {
    @Inject
    lateinit var client: PgPool

    fun doStuff1(): Uni<Void> {
        return client.withTransaction(TransactionPropagation.CONTEXT) { c ->
            // DB related calls using an existing TX 
            // or creating a new one if absent
        }
    }
}

@ApplicationScoped
class Bean2 {
    @Inject
    lateinit var client: PgPool

    @Inject
    lateinit var bean1: Bean1

    fun doStuff2(): Uni<Void> {
        // Start a new TX
        return client.withTransaction(TransactionPropagation.CONTEXT) { c ->
            // Bean1's DB calls will use this just created transaction
            bean1.doStuff1() 
                .onItem.transform { _ ->
                     // DB related calls using the existing TX
                 }
        }
    }
}
Ultranium
  • 332
  • 2
  • 19