Skip to content

Commit

Permalink
feat: Bind values to context in Orchestrator (#106)
Browse files Browse the repository at this point in the history
* refactor: Orchestrate와 Rollback 인터페이스에 Function을 제거한다

* feat: Context binding in orchestrator

* docs: Netx version 0.3.3 to 0.3.4
  • Loading branch information
devxb authored Mar 23, 2024
1 parent 0eccf9d commit 87ff5c1
Show file tree
Hide file tree
Showing 31 changed files with 789 additions and 252 deletions.
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<br>

![version 0.3.3](https://img.shields.io/badge/version-0.3.3-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.3.4](https://img.shields.io/badge/version-0.3.4-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)

Redis-Stream을 지원하는 Saga frame work 입니다.
Expand Down Expand Up @@ -88,7 +88,7 @@ class OrchestratorConfigurer(
fun orderOrchestartor(): Orchestrator<Order, OrderResponse> { // <First Request, Last Response>
return orchestratorFactory.create("orderOrchestrator")
.start(
function = { order -> // its order type
orchestrate = { order -> // its order type
// Start Transaction with your bussiness logic
// something like ... "Check valid seller"
return@start user
Expand All @@ -98,13 +98,19 @@ class OrchestratorConfigurer(
}
)
.joinReactive(
function = { user -> // Before operations response type "User" flow here
orchestrate = { user -> // Before operations response type "User" flow here
// Webflux supports, should return Mono type.
},
// Can skip rollback operation, if you want
)
.joinWithContext(
contextOrchestrate = { context, request ->
context.set("key", request) // save data on context
context.decode("foo", Foo::class) // The context set in the upstream chain can be retrieved.
},
)
.commit(
function = { request ->
orchestrate = { request ->
// When an error occurs, all rollbacks are called from the bottom up,
// starting from the location where the error occurred.
throw IllegalArgumentException("Oops! Something went wrong..")
Expand Down
2 changes: 2 additions & 0 deletions src/main/kotlin/org/rooftop/netx/api/Codec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ interface Codec {
fun <T : Any> encode(data: T): String

fun <T : Any> decode(data: String, type: KClass<T>): T

fun <T : Any> decode(data: String, type: TypeReference<T>): T
}
19 changes: 19 additions & 0 deletions src/main/kotlin/org/rooftop/netx/api/Context.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.rooftop.netx.api

import kotlin.reflect.KClass

data class Context internal constructor(
private val codec: Codec,
internal val contexts: MutableMap<String, String>,
) {

fun <T : Any> set(key: String, value: T) {
contexts[key] = codec.encode(value)
}

fun <T : Any> decodeContext(key: String, type: Class<T>): T = decodeContext(key, type.kotlin)

fun <T : Any> decodeContext(key: String, type: KClass<T>): T = contexts[key]?.let {
codec.decode(it, type)
} ?: throw NullPointerException("Cannot find context by key \"$key\"")
}
6 changes: 6 additions & 0 deletions src/main/kotlin/org/rooftop/netx/api/ContextOrchestrate.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.rooftop.netx.api

fun interface ContextOrchestrate<T : Any, V : Any> {

fun orchestrate(context: Context, request: T): V
}
6 changes: 6 additions & 0 deletions src/main/kotlin/org/rooftop/netx/api/ContextRollback.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.rooftop.netx.api

fun interface ContextRollback<T : Any, V : Any?> {

fun rollback(context: Context, request: T): V
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.rooftop.netx.api

fun interface OrchestrateFunction<T : Any, V : Any> {
fun interface Orchestrate<T : Any, V : Any> {

fun orchestrate(request: T): V
}
53 changes: 41 additions & 12 deletions src/main/kotlin/org/rooftop/netx/api/OrchestrateChain.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,64 @@ import reactor.core.publisher.Mono
interface OrchestrateChain<OriginReq : Any, T : Any, V : Any> {

fun <S : Any> join(
function: OrchestrateFunction<V, S>,
rollback: RollbackFunction<V, *>? = null,
orchestrate: Orchestrate<V, S>,
rollback: Rollback<V, *>? = null,
): DefaultOrchestrateChain<OriginReq, V, S>

fun <S : Any> joinReactive(
function: OrchestrateFunction<V, Mono<S>>,
rollback: RollbackFunction<V, Mono<*>>? = null,
orchestrate: Orchestrate<V, Mono<S>>,
rollback: Rollback<V, Mono<*>>? = null,
): DefaultOrchestrateChain<OriginReq, V, S>

fun <S : Any> joinWithContext(
contextOrchestrate: ContextOrchestrate<V, S>,
contextRollback: ContextRollback<V, *>? = null,
): DefaultOrchestrateChain<OriginReq, V, S>

fun <S : Any> joinReactiveWithContext(
contextOrchestrate: ContextOrchestrate<V, Mono<S>>,
contextRollback: ContextRollback<V, Mono<*>>? = null,
): DefaultOrchestrateChain<OriginReq, V, S>

fun <S : Any> commit(
function: OrchestrateFunction<V, S>,
rollback: RollbackFunction<V, *>? = null,
orchestrate: Orchestrate<V, S>,
rollback: Rollback<V, *>? = null,
): Orchestrator<OriginReq, S>

fun <S : Any> commitReactive(
function: OrchestrateFunction<V, Mono<S>>,
rollback: RollbackFunction<V, Mono<*>>? = null,
orchestrate: Orchestrate<V, Mono<S>>,
rollback: Rollback<V, Mono<*>>? = null,
): Orchestrator<OriginReq, S>

fun <S : Any> commitWithContext(
contextOrchestrate: ContextOrchestrate<V, S>,
contextRollback: ContextRollback<V, *>? = null,
): Orchestrator<OriginReq, S>

fun <S : Any> commitReactiveWithContext(
contextOrchestrate: ContextOrchestrate<V, Mono<S>>,
contextRollback: ContextRollback<V, Mono<*>>? = null,
): Orchestrator<OriginReq, S>

interface Pre<T : Any> {
fun <V : Any> start(
function: OrchestrateFunction<T, V>,
rollback: RollbackFunction<T, *>? = null,
orchestrate: Orchestrate<T, V>,
rollback: Rollback<T, *>? = null,
): DefaultOrchestrateChain<T, T, V>

fun <V : Any> startReactive(
function: OrchestrateFunction<T, Mono<V>>,
rollback: RollbackFunction<T, Mono<*>>? = null,
orchestrate: Orchestrate<T, Mono<V>>,
rollback: Rollback<T, Mono<*>>? = null,
): DefaultOrchestrateChain<T, T, V>

fun <V : Any> startWithContext(
contextOrchestrate: ContextOrchestrate<T, V>,
contextRollback: ContextRollback<T, *>? = null,
): DefaultOrchestrateChain<T, T, V>

fun <V : Any> startReactiveWithContext(
contextOrchestrate: ContextOrchestrate<T, Mono<V>>,
contextRollback: ContextRollback<T, Mono<*>>? = null,
): DefaultOrchestrateChain<T, T, V>
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.rooftop.netx.api

fun interface RollbackFunction<T : Any, V : Any?> {
fun interface Rollback<T : Any, V : Any?> {

fun rollback(request: T): V
}
14 changes: 14 additions & 0 deletions src/main/kotlin/org/rooftop/netx/api/TypeReference.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.rooftop.netx.api

import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type


abstract class TypeReference<T : Any>() {
val type: Type

init {
val superClass: Type = this.javaClass.genericSuperclass
type = (superClass as ParameterizedType).actualTypeArguments[0]
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.rooftop.netx.engine

import org.rooftop.netx.api.Codec
import org.rooftop.netx.api.Context
import org.rooftop.netx.api.TransactionEvent
import org.rooftop.netx.api.TransactionManager
import reactor.core.publisher.Mono
Expand Down Expand Up @@ -42,30 +43,39 @@ internal abstract class AbstractOrchestrateListener<T : Any, V : Any> internal c
castableType = type
}

internal fun Mono<V>.setNextCastableType(): Mono<V> {
return this.doOnNext {
nextOrchestrateListener?.castableType = it::class
nextRollbackOrchestrateListener?.castableType = it::class
internal fun Mono<Pair<V, Context>>.setNextCastableType(): Mono<Pair<V, Context>> {
return this.doOnNext { (request, _) ->
nextOrchestrateListener?.castableType = request::class
nextRollbackOrchestrateListener?.castableType = request::class
}
}

protected fun Mono<*>.getHeldRequest(transactionEvent: TransactionEvent): Mono<T> {
return this.flatMap {
protected fun Mono<OrchestrateEvent>.getHeldRequest(transactionEvent: TransactionEvent): Mono<Pair<T, OrchestrateEvent>> {
return this.flatMap { event ->
requestHolder.getRequest(
"${transactionEvent.transactionId}:$orchestrateSequence",
getCastableType()
)
).map { it to event }
}
}

protected fun Mono<T>.holdRequestIfRollbackable(transactionEvent: TransactionEvent): Mono<T> {
return this.flatMap { request ->
if (!isRollbackable) {
Mono.just(request)
}
requestHolder.setRequest(
"${transactionEvent.transactionId}:$orchestrateSequence",
request
protected fun holdRequestIfRollbackable(request: T, transactionId: String): Mono<T> {
if (!isRollbackable) {
Mono.just(request)
}
return requestHolder.setRequest(
"$transactionId:$orchestrateSequence",
request
)
}

protected fun Mono<Pair<V, Context>>.toOrchestrateEvent(): Mono<OrchestrateEvent> {
return this.map { (response, context) ->
OrchestrateEvent(
orchestratorId = orchestratorId,
orchestrateSequence = orchestrateSequence + 1,
clientEvent = codec.encode(response),
context = codec.encode(context.contexts),
)
}
}
Expand Down Expand Up @@ -105,7 +115,12 @@ internal abstract class AbstractOrchestrateListener<T : Any, V : Any> internal c
orchestrateEvent: OrchestrateEvent,
) {
val rollbackOrchestrateEvent =
OrchestrateEvent(orchestrateEvent.orchestratorId, rollbackSequence, "")
OrchestrateEvent(
orchestrateEvent.orchestratorId,
rollbackSequence,
"",
orchestrateEvent.context,
)
transactionManager.rollback(
transactionId = transactionId,
cause = throwable.message ?: throwable.localizedMessage,
Expand Down
Loading

0 comments on commit 87ff5c1

Please sign in to comment.