All Downloads are FREE. Search and download functionalities are using the official Maven repository.

no.ks.kes.jdbc.saga.SqlServerSagaRepository.kt Maven / Gradle / Ivy

The newest version!
package no.ks.kes.jdbc.saga

import mu.KotlinLogging
import no.ks.kes.jdbc.CmdTable
import no.ks.kes.jdbc.SagaTable
import no.ks.kes.jdbc.TimeoutTable
import no.ks.kes.jdbc.hwm.SqlServerHwmTrackerRepository
import no.ks.kes.lib.Cmd
import no.ks.kes.lib.CmdSerdes
import no.ks.kes.lib.SagaRepository
import no.ks.kes.lib.SagaStateSerdes
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
import org.springframework.jdbc.datasource.DataSourceTransactionManager
import org.springframework.transaction.support.TransactionTemplate
import java.time.OffsetDateTime
import java.time.ZoneOffset
import java.util.*
import javax.sql.DataSource
import kotlin.reflect.KClass

private val log = KotlinLogging.logger {}

class SqlServerSagaRepository(
        dataSource: DataSource,
        private val sagaStateSerdes: SagaStateSerdes,
        private val cmdSerdes: CmdSerdes,
        initialHwm: Long = -1,
        private val schema: String? = null
) : SagaRepository {
    private val template = NamedParameterJdbcTemplate(dataSource)
    private val transactionManager = DataSourceTransactionManager(dataSource)

    override val hwmTracker = SqlServerHwmTrackerRepository(
            template = template,
            schema = schema,
            initialHwm = initialHwm
    )

    override fun transactionally(runnable: () -> Unit) {
        TransactionTemplate(transactionManager).execute {
            try {
                runnable.invoke()
            } catch (e: Exception) {
                log.error("An error was encountered while retrieving and executing saga-timeouts, transaction will be rolled back", e)
                throw e
            }
        }
    }

    override fun getReadyTimeouts(): SagaRepository.Timeout? {
        return template.query("""
            SELECT TOP 1 ${TimeoutTable.sagaSerializationId}, ${TimeoutTable.sagaCorrelationId}, ${TimeoutTable.timeoutId}             
            FROM ${TimeoutTable.qualifiedName(schema)} 
            WITH (XLOCK)
            WHERE ${TimeoutTable.error} = 0
            AND ${TimeoutTable.timeout}  < CURRENT_TIMESTAMP 
        """) { r, _ ->
            SagaRepository.Timeout(
                    sagaSerializationId = r.getString(TimeoutTable.sagaSerializationId),
                    sagaCorrelationId = UUID.fromString(r.getString(TimeoutTable.sagaCorrelationId)),
                    timeoutId = r.getString(TimeoutTable.timeoutId)
            )
        }.singleOrNull()
    }

    override fun deleteTimeout(timeout: SagaRepository.Timeout) {
        template.update(
                """DELETE FROM ${TimeoutTable.qualifiedName(schema)}  
                   WHERE ${TimeoutTable.sagaCorrelationId} = :${TimeoutTable.sagaCorrelationId}
                   AND ${TimeoutTable.sagaSerializationId} = :${TimeoutTable.sagaSerializationId}
                   AND ${TimeoutTable.timeoutId} = :${TimeoutTable.timeoutId}
                """,
                mutableMapOf(
                        TimeoutTable.sagaSerializationId to timeout.sagaSerializationId,
                        TimeoutTable.sagaCorrelationId to timeout.sagaCorrelationId,
                        TimeoutTable.timeoutId to timeout.timeoutId
                )
        )
    }

    override fun  getSagaState(correlationId: UUID, serializationId: String, sagaStateClass: KClass): T? {
        return template.query(
                """
                    SELECT ${SagaTable.data}                                                            
                    FROM ${SagaTable.qualifiedName(schema)}
                    WITH (XLOCK)
                    WHERE ${SagaTable.correlationId} = :${SagaTable.correlationId} 
                    AND ${SagaTable.serializationId} = :${SagaTable.serializationId}""",
                mutableMapOf(
                        SagaTable.correlationId to correlationId,
                        SagaTable.serializationId to serializationId
                )
        ) { r, _ -> r.getString(SagaTable.data) }
                .singleOrNull()
                ?.let {
                    sagaStateSerdes.deserialize(it.toByteArray(), sagaStateClass)
                }
    }

    override fun update(states: Set) {
        log.info { "updating sagas: $states" }

        template.batchUpdate(
                "INSERT INTO ${SagaTable.qualifiedName(schema)} (${SagaTable.correlationId}, ${SagaTable.serializationId}, ${SagaTable.data}) VALUES (:${SagaTable.correlationId}, :${SagaTable.serializationId}, :${SagaTable.data})",
                states.filterIsInstance()
                        .map {
                            mutableMapOf(
                                    SagaTable.correlationId to it.correlationId,
                                    SagaTable.serializationId to it.serializationId,
                                    SagaTable.data to String(sagaStateSerdes.serialize(it.newState)))
                        }
                        .toTypedArray()
        )

        template.batchUpdate(
                "INSERT INTO ${TimeoutTable.qualifiedName(schema)} (${TimeoutTable.sagaCorrelationId}, ${TimeoutTable.sagaSerializationId}, ${TimeoutTable.timeoutId}, ${TimeoutTable.timeout}, ${TimeoutTable.error}) VALUES (:${TimeoutTable.sagaCorrelationId}, :${TimeoutTable.sagaSerializationId}, :${TimeoutTable.timeoutId}, :${TimeoutTable.timeout}, 0)",
                states.filterIsInstance()
                        .flatMap { saga ->
                            saga.timeouts.map {
                                mutableMapOf(
                                        TimeoutTable.sagaCorrelationId to saga.correlationId,
                                        TimeoutTable.sagaSerializationId to saga.serializationId,
                                        TimeoutTable.timeoutId to it.timeoutId,
                                        TimeoutTable.timeout to OffsetDateTime.ofInstant(it.triggerAt, ZoneOffset.UTC)
                                )
                            }
                        }
                        .toTypedArray()
        )

        template.batchUpdate(
                "UPDATE ${SagaTable.qualifiedName(schema)} SET ${SagaTable.data} = :${SagaTable.data} WHERE ${SagaTable.correlationId} = :${SagaTable.correlationId} AND ${SagaTable.serializationId} = :${SagaTable.serializationId}",
                states.filterIsInstance()
                        .filter { it.newState != null }
                        .map {
                            mutableMapOf(
                                    SagaTable.correlationId to it.correlationId,
                                    SagaTable.serializationId to it.serializationId,
                                    SagaTable.data to String(sagaStateSerdes.serialize(it.newState!!)))
                        }
                        .toTypedArray()
        )

        template.batchUpdate(
                """ 
                        INSERT INTO ${CmdTable.qualifiedName(schema)} (${CmdTable.serializationId}, ${CmdTable.aggregateId}, ${CmdTable.retries}, ${CmdTable.nextExecution}, ${CmdTable.error}, ${CmdTable.data}) 
                        VALUES (:${CmdTable.serializationId}, :${CmdTable.aggregateId}, 0, :${CmdTable.nextExecution}, 0, :${CmdTable.data})                        
                        """,
                states.flatMap { it.commands }.map {
                    mutableMapOf(
                            CmdTable.serializationId to cmdSerdes.getSerializationId(it::class as KClass>),
                            CmdTable.aggregateId to it.aggregateId,
                            CmdTable.nextExecution to OffsetDateTime.now(ZoneOffset.UTC),
                            CmdTable.data to String(cmdSerdes.serialize(it)))
                }
                        .toTypedArray())
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy