diff --git a/build.gradle b/build.gradle index 5c60785..01e88e0 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ import org.springframework.boot.gradle.plugin.SpringBootPlugin plugins { id "application" + id 'org.jetbrains.dokka' version "${dokkaVersion}" id "org.jetbrains.kotlin.jvm" version "${jetbrainKotlinVersion}" id "org.jetbrains.kotlin.plugin.spring" version "${jetbrainKotlinVersion}" id "org.springframework.boot" version "${springbootVersion}" apply false @@ -50,6 +51,7 @@ apply from: "gradle/core.gradle" apply from: "gradle/sonar.gradle" apply from: "gradle/kotlin.gradle" apply from: "gradle/spring.gradle" +apply from: "gradle/docs.gradle" wrapper { gradleVersion = "7.6.1" diff --git a/gradle.properties b/gradle.properties index 5cb3c9d..96646eb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,6 +18,7 @@ springMockkVersion=4.0.0 ### Kotlin ### jetbrainKotlinVersion=1.9.22 +dokkaVersion=1.9.20 jvmTarget=17 ### Kotest ### diff --git a/gradle/docs.gradle b/gradle/docs.gradle new file mode 100644 index 0000000..811be29 --- /dev/null +++ b/gradle/docs.gradle @@ -0,0 +1,19 @@ +dokkaHtml { + outputDirectory.set(file("build/documentation/html")) +} + +dokkaGfm { + outputDirectory.set(file("build/documentation/markdown")) +} + +tasks.register('dokkaHtmlJar', Jar.class) { + dependsOn(dokkaHtml) + from(dokkaHtml) + archiveClassifier.set("html-docs") +} + +tasks.register('dokkaJavadocJar', Jar.class) { + dependsOn(dokkaJavadoc) + from(dokkaJavadoc) + archiveClassifier.set("javadoc") +} diff --git a/src/main/kotlin/org/rooftop/netx/api/Context.kt b/src/main/kotlin/org/rooftop/netx/api/Context.kt index edb9a33..b85ccbf 100644 --- a/src/main/kotlin/org/rooftop/netx/api/Context.kt +++ b/src/main/kotlin/org/rooftop/netx/api/Context.kt @@ -1,23 +1,61 @@ package org.rooftop.netx.api +import org.rooftop.netx.core.Codec import kotlin.reflect.KClass +/** + * Context maintained in each saga. + * + * The lifecycle of the Context is per saga request. + * + * @see Orchestrator + * @see ContextOrchestrate + * @see ContextRollback + */ data class Context internal constructor( private val codec: Codec, internal val contexts: MutableMap, ) { + /** + * Sets the value with the specified key in the Context. + * + * Once set, it can be used in subsequent orchestrate and rollback operations. + * + * If the key already exists, it will overwrite the value. + */ fun set(key: String, value: T) { contexts[key] = codec.encode(value) } + /** + * Decodes the value associated with the key using the provided type reference. + * + * Use TypeReference when decoding types with generics. + * + * Example. + * + * context.decodeContext("foos", object: TypeReference>(){}) + * + * @param typeReference + * @param T + */ fun decodeContext(key: String, typeReference: TypeReference): T = contexts[key]?.let { codec.decode(it, typeReference) } ?: throw NullPointerException("Cannot find context by key \"$key\"") + /** + * Decodes the value associated with the key using the provided Class. + * + * @param type + * @param T + */ fun decodeContext(key: String, type: Class): T = decodeContext(key, type.kotlin) + /** + * @see decodeContext + */ fun decodeContext(key: String, type: KClass): T = contexts[key]?.let { codec.decode(it, type) } ?: throw NullPointerException("Cannot find context by key \"$key\"") diff --git a/src/main/kotlin/org/rooftop/netx/api/ContextOrchestrate.kt b/src/main/kotlin/org/rooftop/netx/api/ContextOrchestrate.kt index 3511c86..707bb54 100644 --- a/src/main/kotlin/org/rooftop/netx/api/ContextOrchestrate.kt +++ b/src/main/kotlin/org/rooftop/netx/api/ContextOrchestrate.kt @@ -1,8 +1,23 @@ package org.rooftop.netx.api +/** + * An interface for accessing the Context maintained in each Orchestrator saga. + * + * @see Orchestrate + * @see Context + * @see Orchestrator + */ fun interface ContextOrchestrate : TypeReified { + /** + * Passes the context with the request to orchestrate. + * + * @see Orchestrate.orchestrate + */ fun orchestrate(context: Context, request: T): V + /** + * @see Orchestrate.reified + */ override fun reified(): TypeReference? = null } diff --git a/src/main/kotlin/org/rooftop/netx/api/ContextRollback.kt b/src/main/kotlin/org/rooftop/netx/api/ContextRollback.kt index 5cebbee..9d83185 100644 --- a/src/main/kotlin/org/rooftop/netx/api/ContextRollback.kt +++ b/src/main/kotlin/org/rooftop/netx/api/ContextRollback.kt @@ -1,8 +1,23 @@ package org.rooftop.netx.api +/** + * An interface for accessing the Context maintained in each Orchestrator saga. + * + * @see Orchestrate + * @see Context + * @see Orchestrator + */ fun interface ContextRollback : TypeReified { + /** + * Passes the context with the request to orchestrate. + * + * @see Rollback.rollback + */ fun rollback(context: Context, request: T): V + /** + * @see Rollback.reified + */ override fun reified(): TypeReference? = null } diff --git a/src/main/kotlin/org/rooftop/netx/api/Orchestrate.kt b/src/main/kotlin/org/rooftop/netx/api/Orchestrate.kt index 035c621..e3aa23d 100644 --- a/src/main/kotlin/org/rooftop/netx/api/Orchestrate.kt +++ b/src/main/kotlin/org/rooftop/netx/api/Orchestrate.kt @@ -1,8 +1,28 @@ package org.rooftop.netx.api +/** + * Unit of operation for Orchestrator. + * + * Orchestrate is guaranteed to be executed at least once under any circumstances. + * + * @see Orchestrator + */ fun interface Orchestrate : TypeReified { + /** + * Takes a request and responds with the result of orchestration. + * + * The result of orchestration is passed to the subsequent orchestrate. + * + * @param T request type of Orchestrate + * @param V response type of Orchestrate + */ fun orchestrate(request: T): V - + + /** + * If the request parameter includes generics such as List, this function must be implemented. + * + * Generics prevent type inference, so this function relies on the return value of the reified function to decode and pass the request. + */ override fun reified(): TypeReference? = null } diff --git a/src/main/kotlin/org/rooftop/netx/api/OrchestrateChain.kt b/src/main/kotlin/org/rooftop/netx/api/OrchestrateChain.kt index 55c51b4..9056c4c 100644 --- a/src/main/kotlin/org/rooftop/netx/api/OrchestrateChain.kt +++ b/src/main/kotlin/org/rooftop/netx/api/OrchestrateChain.kt @@ -2,64 +2,146 @@ package org.rooftop.netx.api import reactor.core.publisher.Mono +/** + * Used to create an Orchestrator. + * + * Each operation in the OrchestrateChain is not executed immediately but deferred until the Orchestrator saga is executed. + * + * For detailed usage, refer to the Example of Orchestrator. + * + * @see Orchestrator + * @param OriginReq The first request of Orchestrator + * @param T The request type of each Chain + * @param V The response type of each Chain + */ interface OrchestrateChain { + /** + * Joins the saga with the operation. + * + * @param orchestrate Operation to be executed along with the join + * @param rollback Rollback function to be executed if an exception is thrown in the current orchestrate or sub-orchestrate. + * @param S Response passed as the request to the next orchestrate and rollback. + * @return OrchestrateChain + * @see Orchestrate + * @see Rollback + */ fun join( orchestrate: Orchestrate, rollback: Rollback? = null, ): OrchestrateChain + /** + * @see join + */ fun joinReactive( orchestrate: Orchestrate>, rollback: Rollback>? = null, ): OrchestrateChain + /** + * @param contextOrchestrate Allows using Context maintained in each Saga. + * @param contextRollback Allows using Context maintained in each Saga. + * @see join + * @see ContextOrchestrate + * @see ContextRollback + */ fun joinWithContext( contextOrchestrate: ContextOrchestrate, contextRollback: ContextRollback? = null, ): OrchestrateChain + /** + * @see joinReactiveWithContext + */ fun joinReactiveWithContext( contextOrchestrate: ContextOrchestrate>, contextRollback: ContextRollback>? = null, ): OrchestrateChain + /** + * Commits the saga with the operation. + * + * @param orchestrate Operation to be executed along with the commit. + * @param rollback Rollback function to be executed if an exception is thrown in the current orchestrate. + * @param S The final return value of Orchestrator. + * @return Orchestrator + * @see Orchestrate + * @see Rollback* + */ fun commit( orchestrate: Orchestrate, rollback: Rollback? = null, ): Orchestrator + /** + * @see commit + */ fun commitReactive( orchestrate: Orchestrate>, rollback: Rollback>? = null, ): Orchestrator + /** + * @param contextOrchestrate Allows using Context maintained in each Saga. + * @param contextRollback Allows using Context maintained in each Saga. + * @see commit + * @see contextOrchestrate + * @see contextRollback + */ fun commitWithContext( contextOrchestrate: ContextOrchestrate, contextRollback: ContextRollback? = null, ): Orchestrator + /** + * @see commitWithContext + */ fun commitReactiveWithContext( contextOrchestrate: ContextOrchestrate>, contextRollback: ContextRollback>? = null, ): Orchestrator interface Pre { + + /** + * Starts the saga with the operation. + * + * @param orchestrate Operation to be executed along with the start. + * @param rollback Rollback function to be executed if an exception is thrown in the current orchestrate or sub-orchestrate. + * @param S Response passed as the request to the next orchestrate and rollback. + * @return OrchestrateChain + * @see Orchestrate + * @see Rollback + */ fun start( orchestrate: Orchestrate, rollback: Rollback? = null, ): OrchestrateChain + /** + * @see start + */ fun startReactive( orchestrate: Orchestrate>, rollback: Rollback>? = null, ): OrchestrateChain + /** + * @param contextOrchestrate Allows using Context maintained in each Saga. + * @param contextRollback Allows using Context maintained in each Saga. + * @see start + * @see ContextOrchestrate + * @see ContextRollback + */ fun startWithContext( contextOrchestrate: ContextOrchestrate, contextRollback: ContextRollback? = null, ): OrchestrateChain + /** + * @see startWithContext + */ fun startReactiveWithContext( contextOrchestrate: ContextOrchestrate>, contextRollback: ContextRollback>? = null, diff --git a/src/main/kotlin/org/rooftop/netx/api/OrchestrateRequest.kt b/src/main/kotlin/org/rooftop/netx/api/OrchestrateRequest.kt deleted file mode 100644 index 1807635..0000000 --- a/src/main/kotlin/org/rooftop/netx/api/OrchestrateRequest.kt +++ /dev/null @@ -1,13 +0,0 @@ -package org.rooftop.netx.api - -import kotlin.reflect.KClass - -class OrchestrateRequest( - private val event: String, - private val codec: Codec, -) { - - fun decodeEvent(type: Class): T = decodeEvent(type.kotlin) - - fun decodeEvent(type: KClass): T = codec.decode(event, type) -} diff --git a/src/main/kotlin/org/rooftop/netx/api/Orchestrator.kt b/src/main/kotlin/org/rooftop/netx/api/Orchestrator.kt index 6e23352..ed6032f 100644 --- a/src/main/kotlin/org/rooftop/netx/api/Orchestrator.kt +++ b/src/main/kotlin/org/rooftop/netx/api/Orchestrator.kt @@ -2,21 +2,129 @@ package org.rooftop.netx.api import reactor.core.publisher.Mono +/** + * Allows using saga with orchestration. + * + * If an exception occurs in any OrchestrateChain, rollback is performed starting from that chain upwards. + * Additionally, you can obtain the response if it succeeds via Result or throw the failed exception. + * + * For more details, refer to the Example below. + * + * Example. + * + * class OrderFacade { + * + * private val orderOrchestrator: OrderOrchestrator + * = OrderFactory.instance().get("orderOrchestrator") + * + * fun order(token: String, orderRequest: OrderRequest): Order { + * return orderOrchestrator.sagaSync( + * request = orderRequest, + * context = mapOf("token", token), + * ).decodeResultOrThrow(Order::class) + * } + * + * } + * + * class OrderOrchestratorConfigurer { + * + * private val orchestratorFactory = OrchestratorFactory.instance() + * + * fun orderOrchestrator(): Orchestrator { + * return orchestratorFactory.create("orderOrchestrator") + * .startWithContext( + * contextOrchestrate = { context, orderRequest -> + * val token = context.decodeContext("token", String::class) + * context.set("orderId", orderRequest.id) + * payService.payment(token, orderRequest.id, orderRequest.totalPrice) // Return PayResponse class + * }, + * rollback = { context, orderRequest -> + * val token = context.decodeContext("token", String::class) + * payWebClient.cancelPay(token, orderRequest.id) + * } + * ) + * .joinWithContext( + * contextOrchestrate = { context, payResponse -> + * val orderId = context.decodeContext("orderId", Long::class) + * orderService.successOrder(orderId) // Return Order class + * }, + * rollback = { context, payResponse -> + * val orderId = context.decodeContext("orderId", Long::class) + * orderService.failOrder(orderId) + * } + * ) + * .commit( + * orchestrate = { order -> + * // If a rollback occurs here, all the above rollback functions will be executed sequentially. + * shopService.consumeStock(order.productId, order.productQuantity) + * order + * } + * ) + * } + * } + * + * @see OrchestratorFactory + * @see OrchestrateChain + * @see Orchestrate + * @see ContextOrchestrate + * @see Rollback + * @see ContextRollback + */ interface Orchestrator { + /** + * Executes the saga. + * + * The default timeoutMillis is 10 seconds, and if the result is not returned within this time, ResultTimeoutException is thrown. + * + * However, even if an exception is thrown, the orchestration continues. + * + * @see Result + * @param request + * @return Result returns the result of the saga. + * @throws ResultTimeoutException + */ fun saga(request: T): Mono> + /** + * @see saga + * + * @param timeoutMillis Waits for the result to be returned until timeoutMillis. + */ fun saga(timeoutMillis: Long, request: T): Mono> - fun saga(request: T, context: MutableMap): Mono> + /** + * @see saga + * + * @param context Starts the saga with the context. + */ + fun saga(request: T, context: Map): Mono> - fun saga(timeoutMillis: Long, request: T, context: MutableMap): Mono> + /** + * @see saga + * + * @param timeoutMillis Waits for the result to be returned until timeoutMillis. + * @param context Starts the saga with the context. + */ + fun saga(timeoutMillis: Long, request: T, context: Map): Mono> + /** + * @see saga + */ fun sagaSync(request: T): Result + /** + * @see saga + */ fun sagaSync(timeoutMillis: Long, request: T): Result - fun sagaSync(request: T, context: MutableMap): Result + /** + * @see saga + */ + fun sagaSync(request: T, context: Map): Result - fun sagaSync(timeoutMillis: Long, request: T, context: MutableMap): Result + /** + * @see saga + */ + fun sagaSync(timeoutMillis: Long, request: T, context: Map): Result } diff --git a/src/main/kotlin/org/rooftop/netx/api/OrchestratorFactory.kt b/src/main/kotlin/org/rooftop/netx/api/OrchestratorFactory.kt index c753e3a..5ff37d1 100644 --- a/src/main/kotlin/org/rooftop/netx/api/OrchestratorFactory.kt +++ b/src/main/kotlin/org/rooftop/netx/api/OrchestratorFactory.kt @@ -1,14 +1,43 @@ package org.rooftop.netx.api +/** + * Factory for creating Orchestrators. + * + * For detailed usage, refer to the Example of Orchestrator. + * + * @see Orchestrator + */ interface OrchestratorFactory { + /** + * Returns the Orchestrator corresponding to orchestratorId. + * + * If the Orchestrator corresponding to orchestratorId cannot be found, it throws an IllegalArgumentException. + * + * @param T request of Orchestrator + * @param V response of Orchestrator + * @throws IllegalArgumentException + */ fun get(orchestratorId: String): Orchestrator + /** + * Creates a new Orchestrator. + * + * If an Orchestrator with the same orchestratorId has been created previously, it returns that Orchestrator. + * + * Therefore, Orchestrators with the same orchestratorId are always the same. + * + * @param T request of Orchestrator + * @see OrchestrateChain + */ fun create(orchestratorId: String): OrchestrateChain.Pre companion object Instance { internal lateinit var orchestratorFactory: OrchestratorFactory + /** + * Retrieves the OrchestratorFactory. + */ fun instance(): OrchestratorFactory = orchestratorFactory } } diff --git a/src/main/kotlin/org/rooftop/netx/api/Result.kt b/src/main/kotlin/org/rooftop/netx/api/Result.kt index 73216fc..fac3278 100644 --- a/src/main/kotlin/org/rooftop/netx/api/Result.kt +++ b/src/main/kotlin/org/rooftop/netx/api/Result.kt @@ -1,18 +1,37 @@ package org.rooftop.netx.api +import org.rooftop.netx.core.Codec import kotlin.reflect.KClass +/** + * Returns the result of a Saga. + */ class Result private constructor( + /** + * Indicates whether the Saga was successful. + */ val isSuccess: Boolean, private val codec: Codec, private val result: String?, private val error: Error? = null, ) { + /** + * If failed, throws the exception that caused the failure; if successful, returns the result. + * + * @param typeReference + * @return T result of saga + */ fun decodeResultOrThrow(typeReference: TypeReference): T = decodeResult(typeReference) + /** + * @see decodeResultOrThrow + */ fun decodeResultOrThrow(type: Class): T = decodeResultOrThrow(type.kotlin) + /** + * @see decodeResultOrThrow + */ fun decodeResultOrThrow(type: KClass): T { if (!isSuccess) { throwError() @@ -20,16 +39,37 @@ class Result private constructor( return decodeResult(type) } + /** + * If successful, returns the result. + * + * If the Result cannot be found, throws a ResultException. + * + * @param typeReference + * @return T result of saga + * @throws ResultException + */ fun decodeResult(typeReference: TypeReference): T = result?.let { codec.decode(it, typeReference) } ?: throw ResultException("Cannot decode result cause Result is fail state") + /** + * @see decodeResult + */ fun decodeResult(type: Class): T = decodeResult(type.kotlin) + /** + * @see decodeResult + */ fun decodeResult(type: KClass): T = result?.let { codec.decode(it, type) } ?: throw ResultException("Cannot decode result cause Result is fail state") + /** + * Throws an exception if failed. + * + * If the exception cannot be found, throws a ResultException. + * @throws ResultException + */ fun throwError() = error?.throwError(codec) ?: throw ResultException("Cannot throw error cause Result is success state") diff --git a/src/main/kotlin/org/rooftop/netx/api/Rollback.kt b/src/main/kotlin/org/rooftop/netx/api/Rollback.kt index eca0f89..adea6eb 100644 --- a/src/main/kotlin/org/rooftop/netx/api/Rollback.kt +++ b/src/main/kotlin/org/rooftop/netx/api/Rollback.kt @@ -1,8 +1,23 @@ package org.rooftop.netx.api +/** + * Rollback operation unit of Orchestrator. + * + * Rollback is guaranteed to be executed at least once under any circumstances. + */ fun interface Rollback: TypeReified { + /** + * Receives the request and performs rollback. + * + * The return value of rollback is ignored. + */ fun rollback(request: T): V + /** + * If the request parameter includes generics such as List, this function must be implemented. + * + * Generics prevent type inference, so this function relies on the return value of the reified function to decode and pass the request. + */ override fun reified(): TypeReference? = null } diff --git a/src/main/kotlin/org/rooftop/netx/api/SagaCommitEvent.kt b/src/main/kotlin/org/rooftop/netx/api/SagaCommitEvent.kt index d17827f..ec14e1b 100644 --- a/src/main/kotlin/org/rooftop/netx/api/SagaCommitEvent.kt +++ b/src/main/kotlin/org/rooftop/netx/api/SagaCommitEvent.kt @@ -1,5 +1,11 @@ package org.rooftop.netx.api +import org.rooftop.netx.core.Codec + +/** + * @see SagaEvent + * @see SagaCommitListener + */ class SagaCommitEvent internal constructor( id: String, nodeName: String, diff --git a/src/main/kotlin/org/rooftop/netx/api/SagaCommitListener.kt b/src/main/kotlin/org/rooftop/netx/api/SagaCommitListener.kt index e59fcc3..ebb136d 100644 --- a/src/main/kotlin/org/rooftop/netx/api/SagaCommitListener.kt +++ b/src/main/kotlin/org/rooftop/netx/api/SagaCommitListener.kt @@ -2,6 +2,27 @@ package org.rooftop.netx.api import kotlin.reflect.KClass +/** + * Listener that can receive SagaCommitEvent. Methods annotated with this annotation must specify only one SagaCommitEvent as a parameter, + * and it is guaranteed that they will be executed at least once. even if any problem occurs (such as server shutdown). + * + * If an exception occurs within the method, a RollbackEvent is published. + * + * Example. + * + * @SagaCommitListener // Receives SagaCommitEvent. + * fun listenSagaCommitEvent(sagaCommitEvent: SagaCommitEvent): Foo { + * // ... + * } + * + * @SagaCommitListener(event = Foo::class) // Receives SagaCommitEvent that can be decoded into Foo. + * ... + * + * @SagaCommitListener(noRollbackFor = [IllegalStateException::class]) // If an IllegalStateException occurs during method processing, do not rollback. + * ... + * + * @see SagaManager.commit + */ @Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) annotation class SagaCommitListener( diff --git a/src/main/kotlin/org/rooftop/netx/api/SagaEvent.kt b/src/main/kotlin/org/rooftop/netx/api/SagaEvent.kt index 988c3ab..e07a2d7 100644 --- a/src/main/kotlin/org/rooftop/netx/api/SagaEvent.kt +++ b/src/main/kotlin/org/rooftop/netx/api/SagaEvent.kt @@ -1,29 +1,83 @@ package org.rooftop.netx.api +import org.rooftop.netx.core.Codec import kotlin.reflect.KClass +/** + * SagaEvent passed as a parameter to Saga...Listener. + * + * SagaEvent is Thread safe. + * + * @see SagaStartEvent + * @see SagaJoinEvent + * @see SagaCommitEvent + * @see SagaRollbackEvent + */ sealed class SagaEvent( + /** + * The saga ID. + */ val id: String, + /** + * The name of the node that published the saga event. + */ val nodeName: String, + /** + * The group of the node that published the saga event. + */ val group: String, internal val event: String?, internal val codec: Codec, internal var nextEvent: Any? = null, ) { + /** + * Sets the event to be published when the Saga...Listener is completed. + * + * If nothing is specified, the next Saga state is automatically published along with an empty event. + * + * @see SuccessWith + * @param T next event + * @return T + */ fun setNextEvent(event: T): T { this.nextEvent = event return event } + /** + * Decodes the event using the specified type reference. + * + * Use TypeReference when decoding a type that includes generics. + * + * + * + * Example. + * + * sagaEvent.decodeEvent(object: TypeReference>(){}) + * + * @see SagaManager + * @param typeReference + * @param T + */ fun decodeEvent(typeReference: TypeReference): T = codec.decode( event ?: throw NullPointerException("Cannot decode event cause event is null"), typeReference ) + /** + * Decodes the event using the specified class. + * + * @see SagaManager + * @param type + * @param T + */ fun decodeEvent(type: Class): T = decodeEvent(type.kotlin) + /** + * @see decodeEvent + */ fun decodeEvent(type: KClass): T = codec.decode( event ?: throw NullPointerException("Cannot decode event cause event is null"), diff --git a/src/main/kotlin/org/rooftop/netx/api/SagaJoinEvent.kt b/src/main/kotlin/org/rooftop/netx/api/SagaJoinEvent.kt index 9f3f21f..0a91992 100644 --- a/src/main/kotlin/org/rooftop/netx/api/SagaJoinEvent.kt +++ b/src/main/kotlin/org/rooftop/netx/api/SagaJoinEvent.kt @@ -1,5 +1,11 @@ package org.rooftop.netx.api +import org.rooftop.netx.core.Codec + +/** + * @see SagaEvent + * @see SagaJoinListener + */ class SagaJoinEvent internal constructor( id: String, nodeName: String, diff --git a/src/main/kotlin/org/rooftop/netx/api/SagaJoinListener.kt b/src/main/kotlin/org/rooftop/netx/api/SagaJoinListener.kt index d888dae..1c6bfa3 100644 --- a/src/main/kotlin/org/rooftop/netx/api/SagaJoinListener.kt +++ b/src/main/kotlin/org/rooftop/netx/api/SagaJoinListener.kt @@ -2,6 +2,33 @@ package org.rooftop.netx.api import kotlin.reflect.KClass +/** + * Listener that can receive SagaJoinEvent. Methods annotated with this annotation must specify only one SagaJoinEvent as a parameter, + * and it is guaranteed that they will be executed at least once. even if any problem occurs (such as server shutdown). + * + * If an exception occurs within the method, a RollbackEvent is published, and the event set by sagaEvent.setNextEvent is published along with it. + * + * If the method ends successfully, the event specified by the successWith field is published. Likewise, the event set by sagaEvent.setNextEvent is published along with it. + * + * Example. + * + * @SagaJoinListener // Receives SagaJoinEvent of all types. + * fun listenSagaJoinEvent(sagaJoinEvent: SagaJoinEvent): Foo { + * // ... + * } + * + * @SagaJoinListener(event = Foo::class) // Receives SagaJoinEvent that can be decoded into Foo. + * ... + * + * @SagaJoinListener(noRollbackFor = [IllegalStateException::class]) // If an IllegalStateException occurs during method processing, do not rollback and publish the next event specified by successWith. + * ... + * + * @SagaJoinListener(successWith = SuccessWith.PUBLISH_COMMIT) // You can specify the next event to publish when the method succeeds. + * ... + * + * @see SagaManager.join + * @see SuccessWith + */ @Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) annotation class SagaJoinListener( diff --git a/src/main/kotlin/org/rooftop/netx/api/SagaManager.kt b/src/main/kotlin/org/rooftop/netx/api/SagaManager.kt index bf558ea..acbba7c 100644 --- a/src/main/kotlin/org/rooftop/netx/api/SagaManager.kt +++ b/src/main/kotlin/org/rooftop/netx/api/SagaManager.kt @@ -2,42 +2,155 @@ package org.rooftop.netx.api import reactor.core.publisher.Mono +/** + * Interface for managing the state of a saga (Start, Join, Commit, Rollback) and publishing events. + * Saga events can be received by corresponding Saga...Listener. + * + * Example. + * + * fun startSaga(foo: Foo) { + * sagaManager.startSync(foo) + * } + * + * fun joinSaga(sagaId: String, foo: Foo) { + * sagaManager.joinSync(sagaId, foo) + * } + * + * fun commitSaga(sagaId: String, foo: Foo) { + * sagaManager.commitSync(sagaId, foo) + * } + * + * fun rollbackSaga(sagaId: String, foo: Foo) { + * sagaManager.rollbackSync(sagaId, "Rollback cause invalid name", foo) + * } + * + */ interface SagaManager { + /** + * Start a saga and publish SagaStartEvent. + * + * @see SagaStartEvent + * @see SagaStartListener + * @return String | unique saga id + */ fun start(): Mono + /** + * @see start + * @param T event + */ fun start(event: T): Mono - fun syncStart(): String - - fun syncStart(event: T): String - + /** + * @see start + */ + fun startSync(): String + + /** + * @see start + * @param T event + */ + fun startSync(event: T): String + + /** + * Join the saga identified by id and publish SagaJoinEvent. + * + * @see SagaJoinEvent + * @see SagaJoinListener + * @param id started saga id + * @return String | unique saga id + * @throws SagaException + * @throws AlreadyCommittedSagaException + */ fun join(id: String): Mono + /** + * @see join + * @param T event + */ fun join(id: String, event: T): Mono - fun syncJoin(id: String): String - - fun syncJoin(id: String, event: T): String - + /** + * @see join + */ + fun joinSync(id: String): String + + /** + * @see join + * @param T event + */ + fun joinSync(id: String, event: T): String + + /** + * Check if the saga identified by id exists. + * + * @see SagaException + * @param id started saga id + * @return String | unique saga id + */ fun exists(id: String): Mono - fun syncExists(id: String): String - + /** + * @see exists + */ + fun existsSync(id: String): String + + /** + * Commit the saga identified by id and publish SagaCommitEvent. + * + * @see SagaCommitEvent + * @see SagaCommitListener + * @param id started saga id + * @return String | unique saga id + * @throws SagaException + */ fun commit(id: String): Mono + /** + * @see commit + * @param T event + */ fun commit(id: String, event: T): Mono - fun syncCommit(id: String): String - - fun syncCommit(id: String, event: T): String - + /** + * @see commit + */ + fun commitSync(id: String): String + + /** + * @see commit + * @param T event + */ + fun commitSync(id: String, event: T): String + + /** + * Roll back the saga identified by id and publish SagaRollbackEvent. + * + * @see SagaRollbackEvent + * @see SagaRollbackListener + * @param id started saga id + * @param cause the reason for the rollback + * @return String | unique saga id + * @throws SagaException + */ fun rollback(id: String, cause: String): Mono + /** + * @see rollback + * @param T event + */ fun rollback(id: String, cause: String, event: T): Mono - fun syncRollback(id: String, cause: String): String + /** + * @see rollback + */ + fun rollbackSync(id: String, cause: String): String - fun syncRollback(id: String, cause: String, event: T): String + /** + * @see rollback + * @param T event + */ + fun rollbackSync(id: String, cause: String, event: T): String } diff --git a/src/main/kotlin/org/rooftop/netx/api/SagaRollbackEvent.kt b/src/main/kotlin/org/rooftop/netx/api/SagaRollbackEvent.kt index 998739c..6277846 100644 --- a/src/main/kotlin/org/rooftop/netx/api/SagaRollbackEvent.kt +++ b/src/main/kotlin/org/rooftop/netx/api/SagaRollbackEvent.kt @@ -1,5 +1,11 @@ package org.rooftop.netx.api +import org.rooftop.netx.core.Codec + +/** + * @see SagaEvent + * @see SagaRollbackListener + */ class SagaRollbackEvent internal constructor( id: String, nodeName: String, diff --git a/src/main/kotlin/org/rooftop/netx/api/SagaRollbackListener.kt b/src/main/kotlin/org/rooftop/netx/api/SagaRollbackListener.kt index 3f1c2c4..37dd5fa 100644 --- a/src/main/kotlin/org/rooftop/netx/api/SagaRollbackListener.kt +++ b/src/main/kotlin/org/rooftop/netx/api/SagaRollbackListener.kt @@ -2,6 +2,22 @@ package org.rooftop.netx.api import kotlin.reflect.KClass +/** + * Listener that can receive SagaRollbackEvent. Methods annotated with this annotation must specify only one SagaRollbackEvent as a parameter, + * and it is guaranteed that they will be executed at least once. even if any problem occurs (such as server shutdown). + * + * Example. + * + * @SagaRollbackListener // Receives SagaRollbackEvent of all types. + * fun listenSagaRollbackEvent(sagaRollbackEvent: SagaRollbackEvent): Foo { + * // ... + * } + * + * @SagaRollbackListener(event = Foo::class) // Receives SagaRollbackEvent that can be decoded into Foo. + * ... + * + * @see SagaManager.rollback + */ @Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) annotation class SagaRollbackListener( diff --git a/src/main/kotlin/org/rooftop/netx/api/SagaStartEvent.kt b/src/main/kotlin/org/rooftop/netx/api/SagaStartEvent.kt index 2286c74..e475083 100644 --- a/src/main/kotlin/org/rooftop/netx/api/SagaStartEvent.kt +++ b/src/main/kotlin/org/rooftop/netx/api/SagaStartEvent.kt @@ -1,5 +1,11 @@ package org.rooftop.netx.api +import org.rooftop.netx.core.Codec + +/** + * @see SagaEvent + * @see SagaStartListener + */ class SagaStartEvent internal constructor( id: String, nodeName: String, diff --git a/src/main/kotlin/org/rooftop/netx/api/SagaStartListener.kt b/src/main/kotlin/org/rooftop/netx/api/SagaStartListener.kt index 92407d9..50626c1 100644 --- a/src/main/kotlin/org/rooftop/netx/api/SagaStartListener.kt +++ b/src/main/kotlin/org/rooftop/netx/api/SagaStartListener.kt @@ -2,6 +2,33 @@ package org.rooftop.netx.api import kotlin.reflect.KClass +/** + * Listener that can receive SagaStartEvent. Methods annotated with this annotation must specify only one SagaStartEvent as a parameter, + * and it is guaranteed that they will be executed at least once. even if any problem occurs (such as server shutdown). + * + * If an exception occurs within the method, a RollbackEvent is published, and at this time, the event set by sagaEvent.setNextEvent is also published. + * + * If the method ends successfully, the event set by the successWith field is published. Similarly, the event set by sagaEvent.setNextEvent is also published. + * + * Example. + * + * @SagaStartListener // Receives SagaStartEvent of all types. + * fun listenSagaStartEvent(sagaStartEvent: SagaStartEvent): Foo { + * // ... + * } + * + * @SagaStartListener(event = Foo::class) // Receives SagaStartEvent that can be decoded into Foo. + * ... + * + * @SagaStartListener(noRollbackFor = IllegalStateException::class) // If IllegalStateException occurs during method processing, it does not rollback and publishes the next event set by successWith. + * ... + * + * @SagaStartListener(successWith = SuccessWith.PUBLISH_COMMIT) // You can specify the next event to be published if the method succeeds. + * ... + * + * @see SagaManager.start + * @see SuccessWith + */ @Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) annotation class SagaStartListener( diff --git a/src/main/kotlin/org/rooftop/netx/api/SuccessWith.kt b/src/main/kotlin/org/rooftop/netx/api/SuccessWith.kt index 5a78ca1..09e0133 100644 --- a/src/main/kotlin/org/rooftop/netx/api/SuccessWith.kt +++ b/src/main/kotlin/org/rooftop/netx/api/SuccessWith.kt @@ -1,7 +1,18 @@ package org.rooftop.netx.api enum class SuccessWith { + /** + * Change the saga to the "Join" state and publish an event. + */ PUBLISH_JOIN, + + /** + * Change the saga to the "Commit" state and publish an event. + */ PUBLISH_COMMIT, - END, + + /** + * Terminate the saga without further progression. + */ + END } diff --git a/src/main/kotlin/org/rooftop/netx/api/TypeReference.kt b/src/main/kotlin/org/rooftop/netx/api/TypeReference.kt index 7753c5e..e1915ed 100644 --- a/src/main/kotlin/org/rooftop/netx/api/TypeReference.kt +++ b/src/main/kotlin/org/rooftop/netx/api/TypeReference.kt @@ -3,6 +3,13 @@ package org.rooftop.netx.api import java.lang.reflect.ParameterizedType import java.lang.reflect.Type +/** + * Maintains full-generics-type until runtime by subclassing. + * + * Example. + * + * val typeReference = object: TypeReference>(){ } + */ abstract class TypeReference { val type: Type diff --git a/src/main/kotlin/org/rooftop/netx/api/TypeReified.kt b/src/main/kotlin/org/rooftop/netx/api/TypeReified.kt index 6e4e7a7..80353fc 100644 --- a/src/main/kotlin/org/rooftop/netx/api/TypeReified.kt +++ b/src/main/kotlin/org/rooftop/netx/api/TypeReified.kt @@ -1,5 +1,8 @@ package org.rooftop.netx.api +/** + * Provides a hint about the type by returning a TypeReference. + */ fun interface TypeReified { fun reified(): TypeReference? diff --git a/src/main/kotlin/org/rooftop/netx/api/Codec.kt b/src/main/kotlin/org/rooftop/netx/core/Codec.kt similarity index 75% rename from src/main/kotlin/org/rooftop/netx/api/Codec.kt rename to src/main/kotlin/org/rooftop/netx/core/Codec.kt index 84eb70c..30926b3 100644 --- a/src/main/kotlin/org/rooftop/netx/api/Codec.kt +++ b/src/main/kotlin/org/rooftop/netx/core/Codec.kt @@ -1,5 +1,6 @@ -package org.rooftop.netx.api +package org.rooftop.netx.core +import org.rooftop.netx.api.TypeReference import kotlin.reflect.KClass interface Codec { diff --git a/src/main/kotlin/org/rooftop/netx/engine/AbstractSagaDispatcher.kt b/src/main/kotlin/org/rooftop/netx/engine/AbstractSagaDispatcher.kt index 6a30025..b383ccb 100644 --- a/src/main/kotlin/org/rooftop/netx/engine/AbstractSagaDispatcher.kt +++ b/src/main/kotlin/org/rooftop/netx/engine/AbstractSagaDispatcher.kt @@ -2,6 +2,7 @@ package org.rooftop.netx.engine import jakarta.annotation.PostConstruct import org.rooftop.netx.api.* +import org.rooftop.netx.core.Codec import org.rooftop.netx.engine.core.Saga import org.rooftop.netx.engine.core.SagaState import org.rooftop.netx.engine.logging.info diff --git a/src/main/kotlin/org/rooftop/netx/engine/AbstractSagaManager.kt b/src/main/kotlin/org/rooftop/netx/engine/AbstractSagaManager.kt index 7724ef2..7a11b96 100644 --- a/src/main/kotlin/org/rooftop/netx/engine/AbstractSagaManager.kt +++ b/src/main/kotlin/org/rooftop/netx/engine/AbstractSagaManager.kt @@ -1,7 +1,7 @@ package org.rooftop.netx.engine import org.rooftop.netx.api.AlreadyCommittedSagaException -import org.rooftop.netx.api.Codec +import org.rooftop.netx.core.Codec import org.rooftop.netx.api.SagaException import org.rooftop.netx.api.SagaManager import org.rooftop.netx.engine.core.Saga @@ -18,47 +18,47 @@ internal abstract class AbstractSagaManager( private val sagaIdGenerator: SagaIdGenerator, ) : SagaManager { - override fun syncStart(): String { + override fun startSync(): String { return start().block() ?: throw SagaException("Cannot start saga") } - final override fun syncStart(event: T): String { + final override fun startSync(event: T): String { return start(event).block() ?: throw SagaException("Cannot start saga \"$event\"") } - override fun syncJoin(id: String): String { + override fun joinSync(id: String): String { return join(id).block() ?: throw SagaException("Cannot join saga \"$id\"") } - final override fun syncJoin(id: String, event: T): String { + final override fun joinSync(id: String, event: T): String { return join(id, event).block() ?: throw SagaException("Cannot join saga \"$id\", \"$event\"") } - final override fun syncExists(id: String): String { + final override fun existsSync(id: String): String { return exists(id).block() ?: throw SagaException("Cannot exists saga \"$id\"") } - final override fun syncCommit(id: String): String { + final override fun commitSync(id: String): String { return commit(id).block() ?: throw SagaException("Cannot commit saga \"$id\"") } - override fun syncCommit(id: String, event: T): String { + override fun commitSync(id: String, event: T): String { return commit(id, event).block() ?: throw SagaException("Cannot commit saga \"$id\" \"$event\"") } - final override fun syncRollback(id: String, cause: String): String { + final override fun rollbackSync(id: String, cause: String): String { return rollback(id, cause).block() ?: throw SagaException("Cannot rollback saga \"$id\", \"$cause\"") } - override fun syncRollback(id: String, cause: String, event: T): String { + override fun rollbackSync(id: String, cause: String, event: T): String { return rollback(id, cause, event).block() ?: throw SagaException("Cannot rollback saga \"$id\", \"$cause\" \"$event\"") } diff --git a/src/main/kotlin/org/rooftop/netx/engine/DefaultOrchestrateChain.kt b/src/main/kotlin/org/rooftop/netx/engine/DefaultOrchestrateChain.kt index 2b9793e..811454d 100644 --- a/src/main/kotlin/org/rooftop/netx/engine/DefaultOrchestrateChain.kt +++ b/src/main/kotlin/org/rooftop/netx/engine/DefaultOrchestrateChain.kt @@ -1,6 +1,7 @@ package org.rooftop.netx.engine import org.rooftop.netx.api.* +import org.rooftop.netx.core.Codec import org.rooftop.netx.engine.listen.* import reactor.core.publisher.Mono diff --git a/src/main/kotlin/org/rooftop/netx/engine/JsonCodec.kt b/src/main/kotlin/org/rooftop/netx/engine/JsonCodec.kt index 079a6b8..984d6e7 100644 --- a/src/main/kotlin/org/rooftop/netx/engine/JsonCodec.kt +++ b/src/main/kotlin/org/rooftop/netx/engine/JsonCodec.kt @@ -1,7 +1,7 @@ package org.rooftop.netx.engine import com.fasterxml.jackson.databind.ObjectMapper -import org.rooftop.netx.api.Codec +import org.rooftop.netx.core.Codec import org.rooftop.netx.api.DecodeException import org.rooftop.netx.api.EncodeException import org.rooftop.netx.api.TypeReference diff --git a/src/main/kotlin/org/rooftop/netx/engine/OrchestratorFactory.kt b/src/main/kotlin/org/rooftop/netx/engine/OrchestratorFactory.kt index 8d9a2aa..c3d8bdc 100644 --- a/src/main/kotlin/org/rooftop/netx/engine/OrchestratorFactory.kt +++ b/src/main/kotlin/org/rooftop/netx/engine/OrchestratorFactory.kt @@ -2,6 +2,7 @@ package org.rooftop.netx.engine import org.rooftop.netx.api.* import org.rooftop.netx.api.OrchestratorFactory +import org.rooftop.netx.core.Codec internal class OrchestratorFactory internal constructor( private val sagaManager: SagaManager, diff --git a/src/main/kotlin/org/rooftop/netx/engine/OrchestratorManager.kt b/src/main/kotlin/org/rooftop/netx/engine/OrchestratorManager.kt index 3d27a32..6b60fc6 100644 --- a/src/main/kotlin/org/rooftop/netx/engine/OrchestratorManager.kt +++ b/src/main/kotlin/org/rooftop/netx/engine/OrchestratorManager.kt @@ -1,6 +1,10 @@ package org.rooftop.netx.engine -import org.rooftop.netx.api.* +import org.rooftop.netx.api.Orchestrator +import org.rooftop.netx.api.Result +import org.rooftop.netx.api.SagaException +import org.rooftop.netx.api.SagaManager +import org.rooftop.netx.core.Codec import org.rooftop.netx.engine.listen.AbstractOrchestrateListener import reactor.core.publisher.Mono import kotlin.time.Duration.Companion.milliseconds @@ -24,7 +28,7 @@ internal class OrchestratorManager internal constructor( ?: throw SagaException("Cannot start saga \"$request\"") } - override fun sagaSync(request: T, context: MutableMap): Result { + override fun sagaSync(request: T, context: Map): Result { return saga(request, context).block() ?: throw SagaException("Cannot start saga \"$request\"") } @@ -32,7 +36,7 @@ internal class OrchestratorManager internal constructor( override fun sagaSync( timeoutMillis: Long, request: T, - context: MutableMap + context: Map ): Result { return saga(timeoutMillis, request, context).block() ?: throw SagaException("Cannot start saga \"$request\"") @@ -46,14 +50,14 @@ internal class OrchestratorManager internal constructor( return saga(timeoutMillis, request, mutableMapOf()) } - override fun saga(request: T, context: MutableMap): Mono> { + override fun saga(request: T, context: Map): Mono> { return saga(TEN_SECONDS_TO_TIME_OUT, request, context) } override fun saga( timeoutMillis: Long, request: T, - context: MutableMap + context: Map ): Mono> { return Mono.just(request) .doOnNext { _ -> diff --git a/src/main/kotlin/org/rooftop/netx/engine/listen/AbstractOrchestrateListener.kt b/src/main/kotlin/org/rooftop/netx/engine/listen/AbstractOrchestrateListener.kt index 43decd5..bcf6761 100644 --- a/src/main/kotlin/org/rooftop/netx/engine/listen/AbstractOrchestrateListener.kt +++ b/src/main/kotlin/org/rooftop/netx/engine/listen/AbstractOrchestrateListener.kt @@ -1,6 +1,7 @@ package org.rooftop.netx.engine.listen import org.rooftop.netx.api.* +import org.rooftop.netx.core.Codec import org.rooftop.netx.engine.OrchestrateEvent import org.rooftop.netx.engine.RequestHolder import org.rooftop.netx.engine.ResultHolder diff --git a/src/main/kotlin/org/rooftop/netx/engine/listen/CommitOrchestrateListener.kt b/src/main/kotlin/org/rooftop/netx/engine/listen/CommitOrchestrateListener.kt index 1e17cb0..f0e2a7e 100644 --- a/src/main/kotlin/org/rooftop/netx/engine/listen/CommitOrchestrateListener.kt +++ b/src/main/kotlin/org/rooftop/netx/engine/listen/CommitOrchestrateListener.kt @@ -1,6 +1,7 @@ package org.rooftop.netx.engine.listen import org.rooftop.netx.api.* +import org.rooftop.netx.core.Codec import org.rooftop.netx.engine.OrchestrateEvent import org.rooftop.netx.engine.RequestHolder import org.rooftop.netx.engine.ResultHolder diff --git a/src/main/kotlin/org/rooftop/netx/engine/listen/JoinOrchestrateListener.kt b/src/main/kotlin/org/rooftop/netx/engine/listen/JoinOrchestrateListener.kt index 211f058..1f37c61 100644 --- a/src/main/kotlin/org/rooftop/netx/engine/listen/JoinOrchestrateListener.kt +++ b/src/main/kotlin/org/rooftop/netx/engine/listen/JoinOrchestrateListener.kt @@ -1,6 +1,7 @@ package org.rooftop.netx.engine.listen import org.rooftop.netx.api.* +import org.rooftop.netx.core.Codec import org.rooftop.netx.engine.OrchestrateEvent import org.rooftop.netx.engine.RequestHolder import org.rooftop.netx.engine.ResultHolder diff --git a/src/main/kotlin/org/rooftop/netx/engine/listen/MonoCommitOrchestrateListener.kt b/src/main/kotlin/org/rooftop/netx/engine/listen/MonoCommitOrchestrateListener.kt index f40f410..664d930 100644 --- a/src/main/kotlin/org/rooftop/netx/engine/listen/MonoCommitOrchestrateListener.kt +++ b/src/main/kotlin/org/rooftop/netx/engine/listen/MonoCommitOrchestrateListener.kt @@ -1,6 +1,7 @@ package org.rooftop.netx.engine.listen import org.rooftop.netx.api.* +import org.rooftop.netx.core.Codec import org.rooftop.netx.engine.OrchestrateEvent import org.rooftop.netx.engine.RequestHolder import org.rooftop.netx.engine.ResultHolder diff --git a/src/main/kotlin/org/rooftop/netx/engine/listen/MonoJoinOrchestrateListener.kt b/src/main/kotlin/org/rooftop/netx/engine/listen/MonoJoinOrchestrateListener.kt index f7f8ffa..8a7c5df 100644 --- a/src/main/kotlin/org/rooftop/netx/engine/listen/MonoJoinOrchestrateListener.kt +++ b/src/main/kotlin/org/rooftop/netx/engine/listen/MonoJoinOrchestrateListener.kt @@ -1,6 +1,7 @@ package org.rooftop.netx.engine.listen import org.rooftop.netx.api.* +import org.rooftop.netx.core.Codec import org.rooftop.netx.engine.OrchestrateEvent import org.rooftop.netx.engine.RequestHolder import org.rooftop.netx.engine.ResultHolder diff --git a/src/main/kotlin/org/rooftop/netx/engine/listen/MonoOrchestrateCommand.kt b/src/main/kotlin/org/rooftop/netx/engine/listen/MonoOrchestrateCommand.kt index 33865db..583f437 100644 --- a/src/main/kotlin/org/rooftop/netx/engine/listen/MonoOrchestrateCommand.kt +++ b/src/main/kotlin/org/rooftop/netx/engine/listen/MonoOrchestrateCommand.kt @@ -1,6 +1,7 @@ package org.rooftop.netx.engine.listen import org.rooftop.netx.api.* +import org.rooftop.netx.core.Codec import reactor.core.publisher.Mono internal class MonoOrchestrateCommand( diff --git a/src/main/kotlin/org/rooftop/netx/engine/listen/MonoRollbackCommand.kt b/src/main/kotlin/org/rooftop/netx/engine/listen/MonoRollbackCommand.kt index 2893a3c..2733677 100644 --- a/src/main/kotlin/org/rooftop/netx/engine/listen/MonoRollbackCommand.kt +++ b/src/main/kotlin/org/rooftop/netx/engine/listen/MonoRollbackCommand.kt @@ -1,6 +1,7 @@ package org.rooftop.netx.engine.listen import org.rooftop.netx.api.* +import org.rooftop.netx.core.Codec import reactor.core.publisher.Mono internal class MonoRollbackCommand( diff --git a/src/main/kotlin/org/rooftop/netx/engine/listen/MonoRollbackOrchestrateListener.kt b/src/main/kotlin/org/rooftop/netx/engine/listen/MonoRollbackOrchestrateListener.kt index 180f98f..82d5f95 100644 --- a/src/main/kotlin/org/rooftop/netx/engine/listen/MonoRollbackOrchestrateListener.kt +++ b/src/main/kotlin/org/rooftop/netx/engine/listen/MonoRollbackOrchestrateListener.kt @@ -1,6 +1,7 @@ package org.rooftop.netx.engine.listen import org.rooftop.netx.api.* +import org.rooftop.netx.core.Codec import org.rooftop.netx.engine.OrchestrateEvent import org.rooftop.netx.engine.RequestHolder import org.rooftop.netx.engine.ResultHolder diff --git a/src/main/kotlin/org/rooftop/netx/engine/listen/MonoStartOrchestrateListener.kt b/src/main/kotlin/org/rooftop/netx/engine/listen/MonoStartOrchestrateListener.kt index 192698d..fbeb643 100644 --- a/src/main/kotlin/org/rooftop/netx/engine/listen/MonoStartOrchestrateListener.kt +++ b/src/main/kotlin/org/rooftop/netx/engine/listen/MonoStartOrchestrateListener.kt @@ -1,6 +1,7 @@ package org.rooftop.netx.engine.listen import org.rooftop.netx.api.* +import org.rooftop.netx.core.Codec import org.rooftop.netx.engine.OrchestrateEvent import org.rooftop.netx.engine.RequestHolder import org.rooftop.netx.engine.ResultHolder diff --git a/src/main/kotlin/org/rooftop/netx/engine/listen/OrchestrateCommand.kt b/src/main/kotlin/org/rooftop/netx/engine/listen/OrchestrateCommand.kt index f9f4909..54a6c0b 100644 --- a/src/main/kotlin/org/rooftop/netx/engine/listen/OrchestrateCommand.kt +++ b/src/main/kotlin/org/rooftop/netx/engine/listen/OrchestrateCommand.kt @@ -1,6 +1,7 @@ package org.rooftop.netx.engine.listen import org.rooftop.netx.api.* +import org.rooftop.netx.core.Codec internal class OrchestrateCommand( private val commandType: CommandType = CommandType.DEFAULT, diff --git a/src/main/kotlin/org/rooftop/netx/engine/listen/RollbackCommand.kt b/src/main/kotlin/org/rooftop/netx/engine/listen/RollbackCommand.kt index 59eb0cd..a1dfd7e 100644 --- a/src/main/kotlin/org/rooftop/netx/engine/listen/RollbackCommand.kt +++ b/src/main/kotlin/org/rooftop/netx/engine/listen/RollbackCommand.kt @@ -1,6 +1,7 @@ package org.rooftop.netx.engine.listen import org.rooftop.netx.api.* +import org.rooftop.netx.core.Codec internal class RollbackCommand( private val commandType: CommandType = CommandType.DEFAULT, diff --git a/src/main/kotlin/org/rooftop/netx/engine/listen/RollbackOrchestrateListener.kt b/src/main/kotlin/org/rooftop/netx/engine/listen/RollbackOrchestrateListener.kt index ff8693a..bc1c41e 100644 --- a/src/main/kotlin/org/rooftop/netx/engine/listen/RollbackOrchestrateListener.kt +++ b/src/main/kotlin/org/rooftop/netx/engine/listen/RollbackOrchestrateListener.kt @@ -1,6 +1,7 @@ package org.rooftop.netx.engine.listen import org.rooftop.netx.api.* +import org.rooftop.netx.core.Codec import org.rooftop.netx.engine.OrchestrateEvent import org.rooftop.netx.engine.RequestHolder import org.rooftop.netx.engine.ResultHolder diff --git a/src/main/kotlin/org/rooftop/netx/engine/listen/StartOrchestrateListener.kt b/src/main/kotlin/org/rooftop/netx/engine/listen/StartOrchestrateListener.kt index 91e8404..95b47f7 100644 --- a/src/main/kotlin/org/rooftop/netx/engine/listen/StartOrchestrateListener.kt +++ b/src/main/kotlin/org/rooftop/netx/engine/listen/StartOrchestrateListener.kt @@ -1,6 +1,7 @@ package org.rooftop.netx.engine.listen import org.rooftop.netx.api.* +import org.rooftop.netx.core.Codec import org.rooftop.netx.engine.OrchestrateEvent import org.rooftop.netx.engine.RequestHolder import org.rooftop.netx.engine.ResultHolder diff --git a/src/main/kotlin/org/rooftop/netx/redis/RedisResultHolder.kt b/src/main/kotlin/org/rooftop/netx/redis/RedisResultHolder.kt index b511ff4..66c299b 100644 --- a/src/main/kotlin/org/rooftop/netx/redis/RedisResultHolder.kt +++ b/src/main/kotlin/org/rooftop/netx/redis/RedisResultHolder.kt @@ -2,7 +2,7 @@ package org.rooftop.netx.redis import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonTypeRef -import org.rooftop.netx.api.Codec +import org.rooftop.netx.core.Codec import org.rooftop.netx.api.Result import org.rooftop.netx.api.ResultTimeoutException import org.rooftop.netx.engine.ResultHolder diff --git a/src/main/kotlin/org/rooftop/netx/redis/RedisStreamSagaDispatcher.kt b/src/main/kotlin/org/rooftop/netx/redis/RedisStreamSagaDispatcher.kt index 5683997..3799008 100644 --- a/src/main/kotlin/org/rooftop/netx/redis/RedisStreamSagaDispatcher.kt +++ b/src/main/kotlin/org/rooftop/netx/redis/RedisStreamSagaDispatcher.kt @@ -1,6 +1,6 @@ package org.rooftop.netx.redis -import org.rooftop.netx.api.Codec +import org.rooftop.netx.core.Codec import org.rooftop.netx.api.FailedAckSagaException import org.rooftop.netx.api.SagaManager import org.rooftop.netx.engine.AbstractSagaDispatcher diff --git a/src/main/kotlin/org/rooftop/netx/redis/RedisStreamSagaManager.kt b/src/main/kotlin/org/rooftop/netx/redis/RedisStreamSagaManager.kt index 95981d6..beda185 100644 --- a/src/main/kotlin/org/rooftop/netx/redis/RedisStreamSagaManager.kt +++ b/src/main/kotlin/org/rooftop/netx/redis/RedisStreamSagaManager.kt @@ -1,7 +1,7 @@ package org.rooftop.netx.redis import com.fasterxml.jackson.databind.ObjectMapper -import org.rooftop.netx.api.Codec +import org.rooftop.netx.core.Codec import org.rooftop.netx.api.SagaException import org.rooftop.netx.engine.AbstractSagaManager import org.rooftop.netx.engine.SagaIdGenerator diff --git a/src/test/java/org/rooftop/netx/javasupports/NetxJavaSupportsTest.java b/src/test/java/org/rooftop/netx/javasupports/NetxJavaSupportsTest.java index 6068fdc..dd2718b 100644 --- a/src/test/java/org/rooftop/netx/javasupports/NetxJavaSupportsTest.java +++ b/src/test/java/org/rooftop/netx/javasupports/NetxJavaSupportsTest.java @@ -48,7 +48,7 @@ void clear() { @Test @DisplayName("Scenario-1. Start -> Join -> Commit") void Scenario1_Start_Join_Commit() { - String id = sagaManager.syncStart(POSITIVE_EVENT); + String id = sagaManager.startSync(POSITIVE_EVENT); Awaitility.waitAtMost(5, TimeUnit.SECONDS) .untilAsserted(() -> { @@ -61,7 +61,7 @@ void Scenario1_Start_Join_Commit() { @Test @DisplayName("Scenario-2. Start -> Join -> Rollback") void Scenario2_Start_Join_Rollback() { - String id = sagaManager.syncStart(NEGATIVE_EVENT); + String id = sagaManager.startSync(NEGATIVE_EVENT); Awaitility.waitAtMost(5, TimeUnit.SECONDS) .untilAsserted(() -> { diff --git a/src/test/kotlin/org/rooftop/netx/engine/NetxEventSupportsTest.kt b/src/test/kotlin/org/rooftop/netx/engine/NetxEventSupportsTest.kt index 8162b51..cf9566a 100644 --- a/src/test/kotlin/org/rooftop/netx/engine/NetxEventSupportsTest.kt +++ b/src/test/kotlin/org/rooftop/netx/engine/NetxEventSupportsTest.kt @@ -31,7 +31,7 @@ internal class NetxEventSupportsTest( "event로 객체가 주어지면, SagaRollbackEvent에서 해당 객체를 decode 할 수 있다." { // given val expected = Foo("hello", 1.1234567891234568) - sagaManager.syncStart(expected) + sagaManager.startSync(expected) Thread.sleep(1000) @@ -45,8 +45,8 @@ internal class NetxEventSupportsTest( "event로 Map이 주어지면, SagaRollbackEvent에서 해당 객체를 decode할 수 있다." { // given val expected = mapOf("name" to "hello") - val id = sagaManager.syncStart() - sagaManager.syncJoin(id, expected) + val id = sagaManager.startSync() + sagaManager.joinSync(id, expected) Thread.sleep(1000) @@ -61,8 +61,8 @@ internal class NetxEventSupportsTest( "event로 Int가 주어지면, SagaRollbackEvent에서 해당 객체를 decode할 수 있다." { // given val expected = 1 - val id = sagaManager.syncStart() - sagaManager.syncCommit(id, expected) + val id = sagaManager.startSync() + sagaManager.commitSync(id, expected) Thread.sleep(1000) @@ -76,8 +76,8 @@ internal class NetxEventSupportsTest( "event로 Long이 주어지면, SagaRollbackEvent에서 해당 객체를 decode할 수 있다." { // given val expected = 1L - val id = sagaManager.syncStart() - sagaManager.syncRollback(id, "cause", expected) + val id = sagaManager.startSync() + sagaManager.rollbackSync(id, "cause", expected) Thread.sleep(1000) @@ -91,7 +91,7 @@ internal class NetxEventSupportsTest( "event로 String이 주어지면, SagaRollbackEvent에서 해당 객체를 decode할 수 있다." { // given val expected = "string" - sagaManager.syncStart(expected) + sagaManager.startSync(expected) Thread.sleep(1000) @@ -105,7 +105,7 @@ internal class NetxEventSupportsTest( "event로 char이 주어지면, SagaRollbackEvent에서 해당 객체를 decode할 수 있다." { // given val expected = 'c' - sagaManager.syncStart(expected) + sagaManager.startSync(expected) Thread.sleep(1000) @@ -119,7 +119,7 @@ internal class NetxEventSupportsTest( "event로 Boolean이 주어지면, SagaRollbackEvent에서 해당 객체를 decode할 수 있다." { // given val expected = true - sagaManager.syncStart(expected) + sagaManager.startSync(expected) Thread.sleep(1000) @@ -133,7 +133,7 @@ internal class NetxEventSupportsTest( "event로 Unit이 주어지면, SagaRollbackEvent에서 해당 객체를 decode할 수 있다." { // given val expected = Unit - sagaManager.syncStart(expected) + sagaManager.startSync(expected) Thread.sleep(1000) diff --git a/src/test/kotlin/org/rooftop/netx/engine/NetxEventTypedDispatherTest.kt b/src/test/kotlin/org/rooftop/netx/engine/NetxEventTypedDispatherTest.kt index 1c0aa94..31c274d 100644 --- a/src/test/kotlin/org/rooftop/netx/engine/NetxEventTypedDispatherTest.kt +++ b/src/test/kotlin/org/rooftop/netx/engine/NetxEventTypedDispatherTest.kt @@ -29,7 +29,7 @@ internal class NetxEventTypedDispatherTest( } "event로 Foo 타입의 클래스가 주어지면, Any::class, Foo::class의 모든 핸들러에게 saga event가 전파된다." { - sagaManager.syncStart(Foo("xb")) + sagaManager.startSync(Foo("xb")) eventually(5.seconds) { sagaTypedReceiveStorage.handlerShouldBeEqual(Any::class, 1) @@ -42,7 +42,7 @@ internal class NetxEventTypedDispatherTest( } "event로 String 타입의 클래스가 주어지면, Any::class, String::class의 모든 핸들러에게 saga event가 전파된다." { - sagaManager.syncStart("String") + sagaManager.startSync("String") eventually(5.seconds) { sagaTypedReceiveStorage.handlerShouldBeEqual(Any::class, 1) @@ -55,7 +55,7 @@ internal class NetxEventTypedDispatherTest( } "event로 Long 타입의 클래스가 주어지면, Any::class, Long::class, String::class, Boolean::class 의 모든 핸들러에게 saga event가 전파된다." { - sagaManager.syncStart(1000L) + sagaManager.startSync(1000L) eventually(5.seconds) { sagaTypedReceiveStorage.handlerShouldBeEqual(Any::class, 1) @@ -68,7 +68,7 @@ internal class NetxEventTypedDispatherTest( } "event로 Boolean 타입의 클래스가 주어지면, Any::class, Boolean::class, String::class 의 모든 핸들러에게 saga event가 전파된다." { - sagaManager.syncStart(true) + sagaManager.startSync(true) eventually(5.seconds) { sagaTypedReceiveStorage.handlerShouldBeEqual(Any::class, 1) @@ -81,7 +81,7 @@ internal class NetxEventTypedDispatherTest( } "event로 어떠한것도 전달되지 않으면, Any::class의 모든 핸들러에게 saga event가 전파된다." { - sagaManager.syncStart() + sagaManager.startSync() eventually(5.seconds) { sagaTypedReceiveStorage.handlerShouldBeEqual(Any::class, 1) @@ -94,7 +94,7 @@ internal class NetxEventTypedDispatherTest( } "event로 Unit이 주어지면, Unit::class의 핸들러에게 saga event가 전파된다." { - sagaManager.syncStart(Unit) + sagaManager.startSync(Unit) eventually(5.seconds) { sagaTypedReceiveStorage.handlerShouldBeEqual(Any::class, 1) diff --git a/src/test/kotlin/org/rooftop/netx/redis/RedisStreamSagaDispatcherNoRollbackForTest.kt b/src/test/kotlin/org/rooftop/netx/redis/RedisStreamSagaDispatcherNoRollbackForTest.kt index a225ab9..ec76313 100644 --- a/src/test/kotlin/org/rooftop/netx/redis/RedisStreamSagaDispatcherNoRollbackForTest.kt +++ b/src/test/kotlin/org/rooftop/netx/redis/RedisStreamSagaDispatcherNoRollbackForTest.kt @@ -29,7 +29,7 @@ internal class RedisStreamSagaDispatcherNoRollbackForTest( } "noRollbackFor로 IllegalArgumentException이 걸려있으면, 해당 예외가 발생해도 rollback 하지 않는다." { - sagaManager.syncStart(IllegalArgumentExceptionEvent("illegal")) + sagaManager.startSync(IllegalArgumentExceptionEvent("illegal")) eventually(5.seconds) { sagaAssertions.startCountShouldBe(1) @@ -38,7 +38,7 @@ internal class RedisStreamSagaDispatcherNoRollbackForTest( } "noRollbackFor로 UnSupportedOperationException이 걸려있으면, 해당 예외가 발생해도 rollback 하지않는다." { - sagaManager.syncStart(UnSupportedOperationExceptionEvent("unsupports")) + sagaManager.startSync(UnSupportedOperationExceptionEvent("unsupports")) eventually(5.seconds) { sagaAssertions.startCountShouldBe(1) @@ -47,7 +47,7 @@ internal class RedisStreamSagaDispatcherNoRollbackForTest( } "noRollbackFor에 설정되지 않은 예외가 발생하면, rollback을 수행한다." { - sagaManager.syncStart(NoSuchElementExceptionEvent("noSuchElement")) + sagaManager.startSync(NoSuchElementExceptionEvent("noSuchElement")) eventually(5.seconds) { sagaAssertions.startCountShouldBe(1) diff --git a/src/test/kotlin/org/rooftop/netx/redis/RedisStreamSagaManagerTest.kt b/src/test/kotlin/org/rooftop/netx/redis/RedisStreamSagaManagerTest.kt index 2b0cf21..c71ce8c 100644 --- a/src/test/kotlin/org/rooftop/netx/redis/RedisStreamSagaManagerTest.kt +++ b/src/test/kotlin/org/rooftop/netx/redis/RedisStreamSagaManagerTest.kt @@ -61,7 +61,7 @@ internal class RedisStreamSagaManagerTest( describe("syncStart 메소드는") { context("어떤 event도 없이 호출되면,") { it("Saga 를 시작하고 saga-id를 반환한다.") { - sagaManager.syncStart() + sagaManager.startSync() eventually(5.seconds) { monoSagaHandlerAssertions.startCountShouldBe(1) @@ -72,8 +72,8 @@ internal class RedisStreamSagaManagerTest( context("서로 다른 id의 사가가 여러번 시작되어도") { it("모두 읽을 수 있다.") { - sagaManager.syncStart() - sagaManager.syncStart() + sagaManager.startSync() + sagaManager.startSync() eventually(5.seconds) { monoSagaHandlerAssertions.startCountShouldBe(2) @@ -109,10 +109,10 @@ internal class RedisStreamSagaManagerTest( describe("syncJoin 메소드는") { context("존재하는 sagaId를 입력받으면,") { - val sagaId = sagaManager.syncStart() + val sagaId = sagaManager.startSync() it("Saga 에 참여한다.") { - sagaManager.syncJoin(sagaId) + sagaManager.joinSync(sagaId) eventually(5.seconds) { monoSagaHandlerAssertions.joinCountShouldBe(1) @@ -124,7 +124,7 @@ internal class RedisStreamSagaManagerTest( context("존재하지 않는 sagaId를 입력받으면,") { it("SagaException 을 던진다.") { shouldThrowMessage("Cannot find exists saga by id \"$NOT_EXIST_TX_ID\"") { - sagaManager.syncJoin(NOT_EXIST_TX_ID) + sagaManager.joinSync(NOT_EXIST_TX_ID) } } } @@ -155,10 +155,10 @@ internal class RedisStreamSagaManagerTest( describe("syncExists 메소드는") { context("존재하는 sagaId를 입력받으면,") { - val sagaId = sagaManager.syncStart() + val sagaId = sagaManager.startSync() it("saga id를 반환한다.") { - val result = sagaManager.syncExists(sagaId) + val result = sagaManager.existsSync(sagaId) result shouldBe sagaId } @@ -167,7 +167,7 @@ internal class RedisStreamSagaManagerTest( context("존재하지 않는 sagaId를 입력받으면,") { it("SagaException 을 던진다.") { shouldThrowMessage("Cannot find exists saga by id \"$NOT_EXIST_TX_ID\"") { - sagaManager.syncExists(NOT_EXIST_TX_ID) + sagaManager.existsSync(NOT_EXIST_TX_ID) } } } @@ -199,10 +199,10 @@ internal class RedisStreamSagaManagerTest( describe("syncCommit 메소드는") { context("존재하는 sagaId를 입력받으면,") { - val sagaId = sagaManager.syncStart() + val sagaId = sagaManager.startSync() it("commit 메시지를 publish 한다") { - sagaManager.syncCommit(sagaId) + sagaManager.commitSync(sagaId) eventually(5.seconds) { monoSagaHandlerAssertions.commitCountShouldBe(1) @@ -214,7 +214,7 @@ internal class RedisStreamSagaManagerTest( context("존재하지 않는 sagaId를 입력받으면,") { it("SagaException 을 던진다.") { shouldThrowMessage("Cannot find exists saga by id \"$NOT_EXIST_TX_ID\"") { - sagaManager.syncCommit(NOT_EXIST_TX_ID) + sagaManager.commitSync(NOT_EXIST_TX_ID) } } } @@ -246,10 +246,10 @@ internal class RedisStreamSagaManagerTest( describe("syncRollback 메소드는") { context("존재하는 sagaId를 입력받으면,") { - val sagaId = sagaManager.syncStart() + val sagaId = sagaManager.startSync() it("rollback 메시지를 publish 한다") { - sagaManager.syncRollback(sagaId, "rollback for test") + sagaManager.rollbackSync(sagaId, "rollback for test") eventually(5.seconds) { monoSagaHandlerAssertions.rollbackCountShouldBe(1) @@ -261,7 +261,7 @@ internal class RedisStreamSagaManagerTest( context("존재하지 않는 sagaId를 입력받으면,") { it("SagaException 을 던진다.") { shouldThrowMessage("Cannot find exists saga by id \"$NOT_EXIST_TX_ID\"") { - sagaManager.syncRollback(NOT_EXIST_TX_ID, "rollback for test") + sagaManager.rollbackSync(NOT_EXIST_TX_ID, "rollback for test") } } }