Quarkus事务如何与反应式SQL客户端一起工作

时间:2020-04-08 14:45:25

标签: transactions reactive-programming quarkus

当前,我正在尝试使用Quarkus(1.3.1)构建一些简单的原型应用程序。我正在尝试使用反应式方法,并使用https://quarkus.io/guides/reactive-sql-clients将反应式数据库连接到PostgreSQL。在这里,我使用Kotlin和Mutiny来简化一些事情。

我有以下控制器:

package me.invitation.infrastructure.rest

import io.smallrye.mutiny.Uni
import me.invitation.application.service.InvitationService
import me.invitation.infrastructure.rest.api.InvitationDto
import me.invitation.infrastructure.rest.system.MicroserviceContentType
import org.eclipse.microprofile.context.ThreadContext
import org.slf4j.LoggerFactory
import javax.inject.Inject
import javax.ws.rs.*
import javax.ws.rs.core.Response


@Path("/invitation")
class Resource @Inject constructor(
        private val invitationService: InvitationService,
        private val threadContext: ThreadContext) {


    @POST
    @Consumes(MicroserviceContentType.PUBLIC_RSC_JSON)
    fun postInvitation(invitation: InvitationDto): Uni<Response> {
        return Uni.createFrom()
                .completionStage(
                        threadContext.withContextCapture(
                                invitationService.sendInvite(invitation.email)))
                .map { Response.status(Response.Status.CREATED).build() }
    }

    companion object {
        private val log = LoggerFactory.getLogger(Resource::class.java)
    }
}

InvitationService 实现如下:

package me.invitation.application.service

import io.smallrye.mutiny.Uni
import me.invitation.application.registry.InvitationRegistry
import me.invitation.application.service.mail.MailService
import org.apache.commons.codec.binary.Base32
import org.eclipse.microprofile.config.inject.ConfigProperty
import org.eclipse.microprofile.context.ManagedExecutor
import org.eclipse.microprofile.context.ThreadContext
import org.slf4j.LoggerFactory
import java.security.NoSuchAlgorithmException
import java.security.SecureRandom
import java.time.Duration
import java.time.LocalDateTime
import java.util.*
import java.util.concurrent.CompletableFuture
import java.util.function.Supplier
import javax.enterprise.context.ApplicationScoped
import javax.inject.Inject
import javax.transaction.Transactional

@ApplicationScoped
class ExternalRegistryInvitationService @Inject constructor(
        private val invitationRepository: InvitationRegistry,
        private val mailService: MailService,
        private val threadContext: ThreadContext,
        private val managedExecutor: ManagedExecutor,
        @ConfigProperty(name ="invitation.expiration-ttl-seconds") private val invitationTTLSeconds: Long
) : InvitationService {

    @Transactional
    override fun sendInvite(email: String): CompletableFuture<Unit> {
        return Uni.createFrom()
                  .completionStage(threadContext.withContextCapture(invitationRepository.save(id, email)))
                  .flatMap {wasSaved ->
                                if(wasSaved) {
                                   Uni.createFrom().completionStage(threadContext.withContextCapture(mailService.sendInvitationEmail(email)))
                                } else {
                                    throw InvitationAlreadyPresentException()
                                }
                            }.subscribeAsCompletionStage()
    }
}

在这里,我尝试将记录保存到数据库中,然后发送电子邮件

InvitationRegistry 的实现如下:

package me.invitation.infrastructure.db

import io.smallrye.mutiny.Uni
import io.vertx.mutiny.pgclient.PgPool
import io.vertx.mutiny.sqlclient.Tuple
import me.invitation.application.registry.InvitationRegistry
import java.time.LocalDateTime
import java.util.concurrent.CompletableFuture
import javax.enterprise.context.ApplicationScoped
import javax.inject.Inject
import javax.transaction.Transactional

@ApplicationScoped
class PostgresInvitationRegistry @Inject constructor(private val client: PgPool) : InvitationRegistry {

    private final val insertQuery = "INSERT INTO invitation(id, email, aud_ts_created, aud_ts_last_modified) VALUES ($1, $2, $3, $4) ON CONFLICT DO NOTHING RETURNING id"

    @Transactional(value = Transactional.TxType.MANDATORY)
    override fun save(id: String, email: String): CompletableFuture<Boolean> {
        return client
                .preparedQuery(insertQuery, Tuple.of(id, email, LocalDateTime.now(), LocalDateTime.now()))
                .flatMap { result -> Uni.createFrom().item(result.rowCount() > 0) }
                .subscribeAsCompletionStage()
    }
}

MailService的实现无关紧要,因为它所做的一切都会引发RuntimeException。

在这里,我希望该顶级事务能够回滚。因为 MailService.sendInvitationEmail 引发异常。但是事实并非如此。即使有例外情况,记录也会保留下来。我已经阅读了本指南https://quarkus.io/guides/transaction#reactive-extensionshttps://quarkus.io/guides/context-propagation#usage-example-for-completionstage,并认为声明性事务与Quarkus中的反应式sql一起很好地工作。

我认为反应连接有一个 autocommit 设置,该设置设置为true。但是,无论是在文档中还是在quarkus及其扩展名的来源中都没有找到与自动提交相关的任何内容。令人惊讶的是,甚至对于quarkus主jdbc pool = agroal都没有自动提交设置!

Myabe有人知道如何使Quarkus中的反应式sql客户端使用声明式事务

0 个答案:

没有答案
相关问题