diff --git a/README.md b/README.md index 69a61ac..6fd94cc 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@
-![version 0.4.7](https://img.shields.io/badge/version-0.4.7-black?labelColor=black&style=flat-square) ![jdk 17](https://img.shields.io/badge/minimum_jdk-17-orange?labelColor=black&style=flat-square) ![load-test](https://img.shields.io/badge/load%20test%2010%2C000%2C000-success-brightgreen?labelColor=black&style=flat-square) +![version 0.4.8](https://img.shields.io/badge/version-0.4.8-black?labelColor=black&style=flat-square) ![jdk 17](https://img.shields.io/badge/minimum_jdk-17-orange?labelColor=black&style=flat-square) ![load-test](https://img.shields.io/badge/load%20test%2010%2C000%2C000-success-brightgreen?labelColor=black&style=flat-square) ![redis--stream](https://img.shields.io/badge/-redis--stream-da2020?style=flat-square&logo=Redis&logoColor=white) **TPS(6,000)** on my Macbook air m2(default options). _[link](#Test1-TPS)_ diff --git a/gradle/test.gradle b/gradle/test.gradle index c07246f..7c910c7 100644 --- a/gradle/test.gradle +++ b/gradle/test.gradle @@ -14,6 +14,7 @@ dependencies { testImplementation "org.awaitility:awaitility:${awaitilityVersion}" testImplementation "io.jsonwebtoken:jjwt-api:0.12.5" + testImplementation "org.springframework.boot:spring-boot-starter-web" runtimeOnly "io.jsonwebtoken:jjwt-impl:0.12.5" runtimeOnly "io.jsonwebtoken:jjwt-jackson:0.12.5" } diff --git a/src/main/kotlin/org/rooftop/netx/api/Exceptions.kt b/src/main/kotlin/org/rooftop/netx/api/Exceptions.kt index 4e6b9c3..c29cf7a 100644 --- a/src/main/kotlin/org/rooftop/netx/api/Exceptions.kt +++ b/src/main/kotlin/org/rooftop/netx/api/Exceptions.kt @@ -1,5 +1,7 @@ package org.rooftop.netx.api +import com.fasterxml.jackson.annotation.JsonIgnoreProperties + class EncodeException(message: String, throwable: Throwable) : RuntimeException(message, throwable) class DecodeException(message: String, throwable: Throwable) : RuntimeException(message, throwable) @@ -16,4 +18,5 @@ class FailedAckSagaException(message: String) : RuntimeException(message) class ResultTimeoutException(message: String, throwable: Throwable) : RuntimeException(message, throwable) +@JsonIgnoreProperties(ignoreUnknown = true) class ResultException(message: String) : RuntimeException(message) diff --git a/src/main/kotlin/org/rooftop/netx/redis/RedisResultHolder.kt b/src/main/kotlin/org/rooftop/netx/redis/RedisResultHolder.kt index 66c299b..aa0a248 100644 --- a/src/main/kotlin/org/rooftop/netx/redis/RedisResultHolder.kt +++ b/src/main/kotlin/org/rooftop/netx/redis/RedisResultHolder.kt @@ -2,9 +2,10 @@ package org.rooftop.netx.redis import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonTypeRef -import org.rooftop.netx.core.Codec import org.rooftop.netx.api.Result +import org.rooftop.netx.api.ResultException import org.rooftop.netx.api.ResultTimeoutException +import org.rooftop.netx.core.Codec import org.rooftop.netx.engine.ResultHolder import org.rooftop.netx.engine.logging.info import org.springframework.data.redis.core.ReactiveRedisTemplate @@ -73,10 +74,20 @@ internal class RedisResultHolder( } override fun setFailResult(id: String, result: T): Mono { - val error = Error( - objectMapper.writeValueAsString(result::class.java), - objectMapper.writeValueAsString(result) - ) + val error = runCatching { + Error( + type = objectMapper.writeValueAsString(result::class.java), + error = objectMapper.writeValueAsString(result), + ) + }.getOrElse { + Error( + type = objectMapper.writeValueAsString(ResultException::class.java), + error = objectMapper.writeValueAsString( + ResultException("Cannot encode fail result to json cause \"${it.message}\"") + ), + ) + } + val encodedError = objectMapper.writeValueAsString(error) return reactiveRedisTemplate.opsForList() .leftPush( diff --git a/src/main/kotlin/org/rooftop/netx/redis/RedisSagaConfigurer.kt b/src/main/kotlin/org/rooftop/netx/redis/RedisSagaConfigurer.kt index c8e8903..02c4544 100644 --- a/src/main/kotlin/org/rooftop/netx/redis/RedisSagaConfigurer.kt +++ b/src/main/kotlin/org/rooftop/netx/redis/RedisSagaConfigurer.kt @@ -1,9 +1,6 @@ package org.rooftop.netx.redis -import com.fasterxml.jackson.annotation.JsonAutoDetect -import com.fasterxml.jackson.annotation.JsonCreator -import com.fasterxml.jackson.annotation.JsonIgnore -import com.fasterxml.jackson.annotation.PropertyAccessor +import com.fasterxml.jackson.annotation.* import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule diff --git a/src/test/kotlin/org/rooftop/netx/engine/OrchestratorConfigurer.kt b/src/test/kotlin/org/rooftop/netx/engine/OrchestratorConfigurer.kt index 8e73487..2d8ed38 100644 --- a/src/test/kotlin/org/rooftop/netx/engine/OrchestratorConfigurer.kt +++ b/src/test/kotlin/org/rooftop/netx/engine/OrchestratorConfigurer.kt @@ -10,6 +10,8 @@ import org.rooftop.netx.engine.OrchestratorTest.Companion.rollbackOrchestratorRe import org.rooftop.netx.engine.OrchestratorTest.Companion.upChainResult import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration +import org.springframework.http.HttpStatus +import org.springframework.web.client.HttpClientErrorException import reactor.core.publisher.Mono import java.time.Instant @@ -317,6 +319,16 @@ internal class OrchestratorConfigurer( .commit { "" } } + @Bean(name = ["throwHttpClientErrorExceptionOnStartOrchestrator"]) + fun throwHttpClientErrorExceptionOnStartOrchestrator(): Orchestrator { + return OrchestratorFactory.instance() + .create("throwHttpClientErrorExceptionOnStartOrchestrator") + .startWithContext({ _, _ -> + throw HttpClientErrorException(HttpStatus.UNAUTHORIZED) + }) + .commitWithContext({ _, _ -> "" }) + } + fun interface ListOrchestrate : ContextOrchestrate, List> { diff --git a/src/test/kotlin/org/rooftop/netx/engine/OrchestratorTest.kt b/src/test/kotlin/org/rooftop/netx/engine/OrchestratorTest.kt index 8d922d3..a0faa90 100644 --- a/src/test/kotlin/org/rooftop/netx/engine/OrchestratorTest.kt +++ b/src/test/kotlin/org/rooftop/netx/engine/OrchestratorTest.kt @@ -2,18 +2,21 @@ package org.rooftop.netx.engine import io.jsonwebtoken.JwtException import io.kotest.assertions.nondeterministic.eventually +import io.kotest.assertions.throwables.shouldThrowExactly import io.kotest.assertions.throwables.shouldThrowWithMessage import io.kotest.core.annotation.DisplayName import io.kotest.core.spec.style.DescribeSpec import io.kotest.matchers.equality.shouldBeEqualToComparingFields import io.kotest.matchers.equals.shouldBeEqual import org.rooftop.netx.api.Orchestrator +import org.rooftop.netx.api.ResultException import org.rooftop.netx.api.TypeReference import org.rooftop.netx.meta.EnableSaga import org.rooftop.netx.redis.RedisContainer import org.springframework.beans.factory.annotation.Qualifier import org.springframework.test.context.ContextConfiguration import org.springframework.test.context.TestPropertySource +import org.springframework.web.client.HttpClientErrorException import java.time.Instant import kotlin.time.Duration.Companion.seconds @@ -45,6 +48,7 @@ internal class OrchestratorTest( @Qualifier("throwOnJoinWithContextOrchestrator") private val throwOnJoinWithContextOrchestrator: Orchestrator, List>, @Qualifier("throwOnCommitWithContextOrchestrator") private val throwOnCommitWithContextOrchestrator: Orchestrator, List>, @Qualifier("throwJwtExceptionOnStartOrchestrator") private val throwJwtExceptionOnStartOrchestrator: Orchestrator, + @Qualifier("throwHttpClientErrorExceptionOnStartOrchestrator") private val throwHttpClientErrorExceptionOnStartOrchestrator: Orchestrator, ) : DescribeSpec({ describe("numberOrchestrator 구현채는") { @@ -259,7 +263,7 @@ internal class OrchestratorTest( it("해당 예외를 Result에서 throw한다.") { shouldThrowWithMessage("Throw error for test.") { throwOnStartWithContextOrchestrator.sagaSync(listOf()) - .decodeResultOrThrow(object: TypeReference>(){}) + .decodeResultOrThrow(object : TypeReference>() {}) } } } @@ -270,7 +274,7 @@ internal class OrchestratorTest( it("해당 예외를 Result에서 throw한다.") { shouldThrowWithMessage("Throw error for test.") { throwOnJoinWithContextOrchestrator.sagaSync(listOf()) - .decodeResultOrThrow(object: TypeReference>(){}) + .decodeResultOrThrow(object : TypeReference>() {}) } } } @@ -281,7 +285,7 @@ internal class OrchestratorTest( it("해당 예외를 Result에서 throw한다.") { shouldThrowWithMessage("Throw error for test.") { throwOnCommitWithContextOrchestrator.sagaSync(listOf()) - .decodeResultOrThrow(object: TypeReference>(){}) + .decodeResultOrThrow(object : TypeReference>() {}) } } } @@ -297,6 +301,17 @@ internal class OrchestratorTest( } } } + + describe("throwHttpClientErrorExceptionOnStartOrchestrator 구현채는") { + context("처리할 수 없는 HttpClientErrorException이 던져지면") { + it("ResultException을 Result에 담고 timeout시간안에 예외를 반환한다") { + shouldThrowExactly { + throwHttpClientErrorExceptionOnStartOrchestrator.sagaSync("") + .decodeResultOrThrow(String::class) + } + } + } + } }) { data class Home( val address: String,