From 88b94515fea9707e3aab5a35a674f3745ebf5e88 Mon Sep 17 00:00:00 2001
From: xb205 <62425964+devxb@users.noreply.github.com>
Date: Mon, 5 Feb 2024 15:04:43 +0900
Subject: [PATCH 1/2] =?UTF-8?q?feat:=20Netx=EA=B0=80=20undo=20=EC=83=81?=
=?UTF-8?q?=ED=83=9C=EB=A5=BC=20=EB=94=B0=EB=A1=9C=20=EA=B4=80=EB=A6=AC?=
=?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=ED=95=9C?=
=?UTF-8?q?=EB=8B=A4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 48 +++++++-----
gradle.properties | 3 +
gradle/core.gradle | 2 +
idl | 2 +-
.../rooftop/netx/api/TransactionManager.kt | 6 +-
.../netx/api/TransactionRollbackEvent.kt | 2 +-
.../engine/AbstractTransactionDispatcher.kt | 18 +++--
.../netx/engine/AbstractTransactionManager.kt | 21 +++--
.../org/rooftop/netx/engine/UndoManager.kt | 13 ++++
.../redis/AutoConfigureRedisTransaction.kt | 8 --
.../redis/RedisStreamTransactionDispatcher.kt | 4 +-
.../redis/RedisStreamTransactionManager.kt | 9 ++-
.../netx/redis/RedisTransactionConfigurer.kt | 5 +-
.../rooftop/netx/redis/RedisUndoConfigurer.kt | 40 ++++++++++
.../rooftop/netx/redis/RedisUndoManager.kt | 36 +++++++++
.../META-INF/spring/spring.factories | 3 +
.../org/rooftop/netx/redis/RedisContainer.kt | 5 ++
.../RedisStreamTransactionManagerTest.kt | 1 +
.../netx/redis/RedisUndoManagerTest.kt | 76 +++++++++++++++++++
src/test/resources/application.properties | 2 +
20 files changed, 254 insertions(+), 50 deletions(-)
create mode 100644 src/main/kotlin/org/rooftop/netx/engine/UndoManager.kt
delete mode 100644 src/main/kotlin/org/rooftop/netx/redis/AutoConfigureRedisTransaction.kt
create mode 100644 src/main/kotlin/org/rooftop/netx/redis/RedisUndoConfigurer.kt
create mode 100644 src/main/kotlin/org/rooftop/netx/redis/RedisUndoManager.kt
create mode 100644 src/test/kotlin/org/rooftop/netx/redis/RedisUndoManagerTest.kt
diff --git a/README.md b/README.md
index 0fe8778..c8f98d0 100644
--- a/README.md
+++ b/README.md
@@ -3,15 +3,16 @@
> Distributed transaction library based on Choreography
-
- 
+
+ 
Choreography 방식으로 구현된 분산 트랜잭션 라이브러리 입니다.
-`Netx` 는 다음 기능을 제공합니다.
+`Netx` 는 다음 기능을 제공합니다.
+
1. [Reactor](https://projectreactor.io/) 기반의 완전한 비동기 트랜잭션 관리
-2. Redis-stream 기반의 트랜잭션 관리
+2. Redis-stream 기반의 트랜잭션 관리
3. 여러 노드가 중복 트랜잭션 이벤트를 수신하는 문제 방지
4. `At Least Once` 방식의 메시지 전달 보장
@@ -34,17 +35,19 @@ class Application {
}
```
-`@AutoconfigureRedisTransaction` 어노테이션으로 자동 구성할 경우 netx는 아래 프로퍼티를 사용해 메시지 큐와 커넥션을 맺습니다.
+`@AutoconfigureRedisTransaction` 어노테이션으로 자동 구성할 경우 netx는 아래 프로퍼티를 사용해 메시지 큐와 커넥션을 맺습니다.
#### Properties
-| key | example | description |
-|------------------|----------|------------------------------------------------------------------------------------------------------------------------------------|
-| **netx.host** | localhost | 트랜잭션 관리에 사용할 메시지 큐 의 host url 입니다. (ex. redis host) |
-| **netx.port** | 6379 | 트랜잭션 관리에 사용할 메시지 큐의 port 입니다. |
-| **netx.group** | pay-group | 분산 노드의 그룹입니다. 트랜잭션 이벤트는 같은 그룹내 하나의 노드로만 전송됩니다. |
-| **netx.node-id** | 1 | id 생성에 사용될 식별자입니다. 모든 서버는 반드시 다른 id를 할당받아야 하며, 1~256 만큼의 id를 설정할 수 있습니다. _`중복된 id 생성을 방지하기위해 twitter snowflake 알고리즘으로 id를 생성합니다.`_ |
-| **netx.node-name** | pay-1 | _`$netx.group`_ 에 참여할 서버의 이름입니다. 같은 그룹내에 중복된 이름이 존재하면 안됩니다. |
+| key | example | description |
+|--------------------|-----------|------------------------------------------------------------------------------------------------------------------------------------|
+| **netx.host** | localhost | 트랜잭션 관리에 사용할 메시지 큐 의 host url 입니다. (ex. redis host) |
+| **netx.port** | 6379 | 트랜잭션 관리에 사용할 메시지 큐의 port 입니다. |
+| **netx.group** | pay-group | 분산 노드의 그룹입니다. 트랜잭션 이벤트는 같은 그룹내 하나의 노드로만 전송됩니다. |
+| **netx.node-id** | 1 | id 생성에 사용될 식별자입니다. 모든 서버는 반드시 다른 id를 할당받아야 하며, 1~256 만큼의 id를 설정할 수 있습니다. _`중복된 id 생성을 방지하기위해 twitter snowflake 알고리즘으로 id를 생성합니다.`_ |
+| **netx.node-name** | pay-1 | _`$netx.group`_ 에 참여할 서버의 이름입니다. 같은 그룹내에 중복된 이름이 존재하면 안됩니다. |
+| **netx.undo.host** | localhost | 트랜잭션 관리에 사용할 메시지 큐 의 host url 입니다. (ex. redis host) |
+| **netx.undo.port** | 6380 | 트랜잭션 관리에 사용할 메시지 큐의 port 입니다. |
### Usage example
@@ -55,21 +58,27 @@ fun pay(param: Any): Mono {
return transactionManager.start("paid=1000") // Start distributed transaction and publish transaction start event
.flatMap { transactionId ->
service.pay(param)
- .doOnError { throwable ->
- transactionManager.rollback(transactionId, throwable.message) // Publish rollback event to all transaction joined node
+ .doOnError { throwable ->
+ transactionManager.rollback(
+ transactionId,
+ throwable.message
+ ) // Publish rollback event to all transaction joined node
}
}.doOnSuccess { transactionId ->
transactionManager.commit(transactionId) // Publish commit event to all transaction joined node
}
}
```
-
+
#### Scenario2. Join order transaction
```kotlin
fun order(param: Any): Mono {
- return transactionManager.join(param.transactionId, "orderId=1:state=PENDING") // join exists distributed transaction and publish transaction join event
- .flatMap { transactionId ->
+ return transactionManager.join(
+ param.transactionId,
+ "orderId=1:state=PENDING"
+ ) // join exists distributed transaction and publish transaction join event
+ .flatMap { transactionId ->
service.order(param)
.doOnError { throwable ->
transactionManager.rollback(transactionId, throwable.message)
@@ -79,7 +88,7 @@ fun order(param: Any): Mono {
}
}
```
-
+
#### Scenario3. Check exists transaction
```kotlin
@@ -90,7 +99,8 @@ fun exists(param: Any): Mono {
#### Scenario4. Handle transaction event
-다른 분산서버가 (혹은 자기자신이) transactionManager를 통해서 트랜잭션을 시작하거나 트랜잭션 상태를 변경했을때, 호출한 메소드에 맞는 트랜잭션 이벤트를 발행합니다.
+다른 분산서버가 (혹은 자기자신이) transactionManager를 통해서 트랜잭션을 시작하거나 트랜잭션 상태를 변경했을때, 호출한 메소드에 맞는 트랜잭션 이벤트를
+발행합니다.
이 이벤트들을 핸들링 함으로써, 다른서버에서 발생한 에러등을 수신하고 롤백할 수 있습니다.
```kotlin
diff --git a/gradle.properties b/gradle.properties
index d66db86..857598c 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -36,3 +36,6 @@ lettuceVersion=6.3.0.RELEASE
### TestContainer ###
testContainerVersion=1.19.3
+
+### Jackson ###
+jacksonVersion=2.16.1
diff --git a/gradle/core.gradle b/gradle/core.gradle
index 433383f..71fa511 100644
--- a/gradle/core.gradle
+++ b/gradle/core.gradle
@@ -1,3 +1,5 @@
dependencies {
implementation "com.github.f4b6a3:tsid-creator:${snowflakeVersion}"
+ implementation "com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}"
+ implementation "com.fasterxml.jackson.module:jackson-module-parameter-names:${jacksonVersion}"
}
diff --git a/idl b/idl
index e1f481d..d0ea8e0 160000
--- a/idl
+++ b/idl
@@ -1 +1 @@
-Subproject commit e1f481d6f34e879b82487603c11cb7a57cc6a6ab
+Subproject commit d0ea8e0b64253d3e7966e2b6d95a751f40fe27de
diff --git a/src/main/kotlin/org/rooftop/netx/api/TransactionManager.kt b/src/main/kotlin/org/rooftop/netx/api/TransactionManager.kt
index 74fc6d7..cc0b9d2 100644
--- a/src/main/kotlin/org/rooftop/netx/api/TransactionManager.kt
+++ b/src/main/kotlin/org/rooftop/netx/api/TransactionManager.kt
@@ -4,11 +4,11 @@ import reactor.core.publisher.Mono
interface TransactionManager {
- fun start(replay: String): Mono
+ fun start(undo: String): Mono
- fun exists(transactionId: String): Mono
+ fun join(transactionId: String, undo: String): Mono
- fun join(transactionId: String, replay: String): Mono
+ fun exists(transactionId: String): Mono
fun commit(transactionId: String): Mono
diff --git a/src/main/kotlin/org/rooftop/netx/api/TransactionRollbackEvent.kt b/src/main/kotlin/org/rooftop/netx/api/TransactionRollbackEvent.kt
index ffa9d2a..addfdce 100644
--- a/src/main/kotlin/org/rooftop/netx/api/TransactionRollbackEvent.kt
+++ b/src/main/kotlin/org/rooftop/netx/api/TransactionRollbackEvent.kt
@@ -2,7 +2,7 @@ package org.rooftop.netx.api
data class TransactionRollbackEvent(
val transactionId: String,
- val replay: String,
val nodeName: String,
val cause: String?,
+ val undoState: String,
)
diff --git a/src/main/kotlin/org/rooftop/netx/engine/AbstractTransactionDispatcher.kt b/src/main/kotlin/org/rooftop/netx/engine/AbstractTransactionDispatcher.kt
index b15af24..3450aac 100644
--- a/src/main/kotlin/org/rooftop/netx/engine/AbstractTransactionDispatcher.kt
+++ b/src/main/kotlin/org/rooftop/netx/engine/AbstractTransactionDispatcher.kt
@@ -11,12 +11,14 @@ import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
abstract class AbstractTransactionDispatcher(
+ private val undoManager: UndoManager,
private val eventPublisher: EventPublisher,
) {
@EventListener(SubscribeTransactionEvent::class)
fun subscribeStream(event: SubscribeTransactionEvent): Flux {
- return receive(event).dispatch()
+ return receive(event)
+ .dispatch()
}
protected abstract fun receive(event: SubscribeTransactionEvent): Flux
@@ -50,18 +52,20 @@ abstract class AbstractTransactionDispatcher(
.doOnNext { eventPublisher.publish(TransactionCommitEvent(it.id, it.serverId)) }
}
- private fun publishRollback(it: Transaction): Mono {
- return Mono.just(it)
+ private fun publishRollback(transaction: Transaction): Mono {
+ return undoManager.find(transaction.id)
.doOnNext {
eventPublisher.publish(
TransactionRollbackEvent(
- it.id,
- it.replay,
- it.serverId,
- it.cause,
+ transaction.id,
+ transaction.serverId,
+ transaction.cause,
+ it
)
)
}
+ .flatMap { undoManager.delete(transaction.id) }
+ .map { transaction }
}
private fun publishStart(it: Transaction): Mono {
diff --git a/src/main/kotlin/org/rooftop/netx/engine/AbstractTransactionManager.kt b/src/main/kotlin/org/rooftop/netx/engine/AbstractTransactionManager.kt
index 160a0d7..0a77b6b 100644
--- a/src/main/kotlin/org/rooftop/netx/engine/AbstractTransactionManager.kt
+++ b/src/main/kotlin/org/rooftop/netx/engine/AbstractTransactionManager.kt
@@ -11,39 +11,40 @@ abstract class AbstractTransactionManager(
private val nodeName: String,
private val eventPublisher: EventPublisher,
private val transactionIdGenerator: TransactionIdGenerator = TransactionIdGenerator(nodeId),
+ private val undoManager: UndoManager,
) : TransactionManager {
- final override fun start(replay: String): Mono {
- return startTransaction(replay)
+ final override fun start(undo: String): Mono {
+ return startTransaction()
.subscribeTransaction()
+ .saveUndoState(undo)
.contextWrite { it.put(CONTEXT_TX_KEY, transactionIdGenerator.generate()) }
}
- private fun startTransaction(replay: String): Mono {
+ private fun startTransaction(): Mono {
return Mono.deferContextual { Mono.just(it[CONTEXT_TX_KEY]) }
.flatMap { transactionId ->
publishTransaction(transactionId, transaction {
id = transactionId
serverId = nodeName
- this.replay = replay
this.state = TransactionState.TRANSACTION_STATE_START
})
}
}
- final override fun join(transactionId: String, replay: String): Mono {
+ final override fun join(transactionId: String, undo: String): Mono {
return exists(transactionId)
- .joinTransaction(replay)
+ .joinTransaction()
.subscribeTransaction()
+ .saveUndoState(undo)
.contextWrite { it.put(CONTEXT_TX_KEY, transactionId) }
}
- private fun Mono.joinTransaction(replay: String): Mono {
+ private fun Mono.joinTransaction(): Mono {
return flatMap { transactionId ->
publishTransaction(transactionId, transaction {
id = transactionId
serverId = nodeName
- this.replay = replay
state = TransactionState.TRANSACTION_STATE_JOIN
})
}
@@ -55,6 +56,10 @@ abstract class AbstractTransactionManager(
}
}
+ private fun Mono.saveUndoState(undo: String): Mono {
+ return this.flatMap { undoManager.save(it, undo) }
+ }
+
final override fun rollback(transactionId: String, cause: String): Mono {
return exists(transactionId)
.publishTransaction(transaction {
diff --git a/src/main/kotlin/org/rooftop/netx/engine/UndoManager.kt b/src/main/kotlin/org/rooftop/netx/engine/UndoManager.kt
new file mode 100644
index 0000000..ab12d9e
--- /dev/null
+++ b/src/main/kotlin/org/rooftop/netx/engine/UndoManager.kt
@@ -0,0 +1,13 @@
+package org.rooftop.netx.engine
+
+import reactor.core.publisher.Mono
+
+interface UndoManager {
+
+ fun save(transactionId: String, undo: String): Mono
+
+ fun find(transactionId: String): Mono
+
+ fun delete(transactionId: String): Mono
+
+}
diff --git a/src/main/kotlin/org/rooftop/netx/redis/AutoConfigureRedisTransaction.kt b/src/main/kotlin/org/rooftop/netx/redis/AutoConfigureRedisTransaction.kt
deleted file mode 100644
index 12cefa0..0000000
--- a/src/main/kotlin/org/rooftop/netx/redis/AutoConfigureRedisTransaction.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.rooftop.netx.redis
-
-import org.springframework.boot.autoconfigure.ImportAutoConfiguration
-
-@ImportAutoConfiguration(RedisTransactionConfigurer::class)
-@Target(AnnotationTarget.CLASS)
-@Retention(AnnotationRetention.RUNTIME)
-annotation class AutoConfigureRedisTransaction
diff --git a/src/main/kotlin/org/rooftop/netx/redis/RedisStreamTransactionDispatcher.kt b/src/main/kotlin/org/rooftop/netx/redis/RedisStreamTransactionDispatcher.kt
index 3390a3c..fc5e7d7 100644
--- a/src/main/kotlin/org/rooftop/netx/redis/RedisStreamTransactionDispatcher.kt
+++ b/src/main/kotlin/org/rooftop/netx/redis/RedisStreamTransactionDispatcher.kt
@@ -2,6 +2,7 @@ package org.rooftop.netx.redis
import org.rooftop.netx.engine.AbstractTransactionDispatcher
import org.rooftop.netx.engine.SubscribeTransactionEvent
+import org.rooftop.netx.engine.UndoManager
import org.rooftop.netx.idl.Transaction
import org.springframework.context.ApplicationEventPublisher
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory
@@ -18,10 +19,11 @@ import kotlin.time.toJavaDuration
class RedisStreamTransactionDispatcher(
eventPublisher: ApplicationEventPublisher,
connectionFactory: ReactiveRedisConnectionFactory,
+ undoManager: UndoManager,
private val streamGroup: String,
private val nodeName: String,
private val reactiveRedisTemplate: ReactiveRedisTemplate,
-) : AbstractTransactionDispatcher(SpringEventPublisher(eventPublisher)) {
+) : AbstractTransactionDispatcher(undoManager, SpringEventPublisher(eventPublisher)) {
private val options = StreamReceiver.StreamReceiverOptions.builder()
.pollTimeout(1.hours.toJavaDuration())
diff --git a/src/main/kotlin/org/rooftop/netx/redis/RedisStreamTransactionManager.kt b/src/main/kotlin/org/rooftop/netx/redis/RedisStreamTransactionManager.kt
index 8d0c31f..2e37ef6 100644
--- a/src/main/kotlin/org/rooftop/netx/redis/RedisStreamTransactionManager.kt
+++ b/src/main/kotlin/org/rooftop/netx/redis/RedisStreamTransactionManager.kt
@@ -1,6 +1,7 @@
package org.rooftop.netx.redis
import org.rooftop.netx.engine.AbstractTransactionManager
+import org.rooftop.netx.engine.UndoManager
import org.rooftop.netx.idl.Transaction
import org.springframework.context.ApplicationEventPublisher
import org.springframework.data.domain.Range
@@ -12,8 +13,14 @@ class RedisStreamTransactionManager(
nodeId: Int,
nodeName: String,
applicationEventPublisher: ApplicationEventPublisher,
+ undoManager: UndoManager,
private val reactiveRedisTemplate: ReactiveRedisTemplate,
-) : AbstractTransactionManager(nodeId, nodeName, SpringEventPublisher(applicationEventPublisher)) {
+) : AbstractTransactionManager(
+ nodeId,
+ nodeName,
+ SpringEventPublisher(applicationEventPublisher),
+ undoManager = undoManager
+) {
override fun findAnyTransaction(transactionId: String): Mono {
return reactiveRedisTemplate.opsForStream()
diff --git a/src/main/kotlin/org/rooftop/netx/redis/RedisTransactionConfigurer.kt b/src/main/kotlin/org/rooftop/netx/redis/RedisTransactionConfigurer.kt
index eb16a91..272409c 100644
--- a/src/main/kotlin/org/rooftop/netx/redis/RedisTransactionConfigurer.kt
+++ b/src/main/kotlin/org/rooftop/netx/redis/RedisTransactionConfigurer.kt
@@ -1,6 +1,7 @@
package org.rooftop.netx.redis
import org.rooftop.netx.api.TransactionManager
+import org.rooftop.netx.engine.UndoManager
import org.rooftop.pay.infra.transaction.ByteArrayRedisSerializer
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.ApplicationEventPublisher
@@ -20,15 +21,16 @@ class RedisTransactionConfigurer(
@Value("\${netx.node-id}") private val nodeId: Int,
@Value("\${netx.node-name}") private val nodeName: String,
private val applicationEventPublisher: ApplicationEventPublisher,
+ private val undoManager: UndoManager,
) {
-
@Bean
fun redisStreamTransactionManager(): TransactionManager =
RedisStreamTransactionManager(
nodeId,
nodeName,
applicationEventPublisher,
+ undoManager,
reactiveRedisTemplate()
)
@@ -37,6 +39,7 @@ class RedisTransactionConfigurer(
RedisStreamTransactionDispatcher(
applicationEventPublisher,
reactiveRedisConnectionFactory(),
+ undoManager,
group,
nodeName,
reactiveRedisTemplate()
diff --git a/src/main/kotlin/org/rooftop/netx/redis/RedisUndoConfigurer.kt b/src/main/kotlin/org/rooftop/netx/redis/RedisUndoConfigurer.kt
new file mode 100644
index 0000000..603770c
--- /dev/null
+++ b/src/main/kotlin/org/rooftop/netx/redis/RedisUndoConfigurer.kt
@@ -0,0 +1,40 @@
+package org.rooftop.netx.redis
+
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory
+import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory
+import org.springframework.data.redis.core.ReactiveRedisTemplate
+import org.springframework.data.redis.serializer.RedisSerializationContext
+import org.springframework.data.redis.serializer.StringRedisSerializer
+
+@Configuration
+class RedisUndoConfigurer(
+ @Value("\${netx.group}") private val group: String,
+ @Value("\${netx.undo.host}") private val netxUndoHost: String,
+ @Value("\${netx.undo.port}") private val netxUndoPort: String,
+) {
+
+ @Bean
+ fun redisUndoManager(): RedisUndoManager = RedisUndoManager(group, redisUndoServer())
+
+ @Bean
+ fun redisUndoServer(): ReactiveRedisTemplate {
+ val stringRedisSerializer = StringRedisSerializer()
+
+ val context =
+ RedisSerializationContext.newSerializationContext(stringRedisSerializer)
+ .value(stringRedisSerializer)
+ .build()
+
+ return ReactiveRedisTemplate(undoServerConnectionFactory(), context)
+ }
+
+ @Bean
+ fun undoServerConnectionFactory(): ReactiveRedisConnectionFactory {
+ val undoServerPort: String = System.getProperty("netx.undo.port") ?: netxUndoPort
+
+ return LettuceConnectionFactory(netxUndoHost, undoServerPort.toInt())
+ }
+}
diff --git a/src/main/kotlin/org/rooftop/netx/redis/RedisUndoManager.kt b/src/main/kotlin/org/rooftop/netx/redis/RedisUndoManager.kt
new file mode 100644
index 0000000..11d18c5
--- /dev/null
+++ b/src/main/kotlin/org/rooftop/netx/redis/RedisUndoManager.kt
@@ -0,0 +1,36 @@
+package org.rooftop.netx.redis
+
+import org.rooftop.netx.engine.UndoManager
+import org.springframework.data.redis.core.ReactiveRedisTemplate
+import reactor.core.publisher.Mono
+
+class RedisUndoManager(
+ private val group: String,
+ private val reactiveRedisTemplate: ReactiveRedisTemplate,
+) : UndoManager {
+
+ override fun find(transactionId: String): Mono {
+ return reactiveRedisTemplate.opsForValue()["$group:$transactionId"]
+ .switchIfEmpty(
+ Mono.error {
+ throw IllegalStateException("Cannot find undo state \"$group:$transactionId\"")
+ }
+ )
+ }
+
+ override fun delete(transactionId: String): Mono {
+ return reactiveRedisTemplate.opsForValue().delete("$group:$transactionId")
+ }
+
+ override fun save(transactionId: String, undo: String): Mono {
+ return reactiveRedisTemplate.opsForValue()
+ .set("$group:$transactionId", undo)
+ .flatMap {
+ when (it) {
+ true -> Mono.just(it)
+ false -> Mono.error { throw IllegalStateException("Error occurred during the undo process.") }
+ }
+ }
+ .map { transactionId }
+ }
+}
diff --git a/src/main/resources/META-INF/spring/spring.factories b/src/main/resources/META-INF/spring/spring.factories
index 18fe703..0a97812 100644
--- a/src/main/resources/META-INF/spring/spring.factories
+++ b/src/main/resources/META-INF/spring/spring.factories
@@ -1,2 +1,5 @@
org.rooftop.netx.redis.AutoConfigureRedisTransaction=\
org.rooftop.netx.redis.RedisTransactionConfigurer
+
+org.rooftop.netx.redis.AutoConfigureRedisUndo=\
+org.rooftop.netx.redis.RedisUndoConfigurer
diff --git a/src/test/kotlin/org/rooftop/netx/redis/RedisContainer.kt b/src/test/kotlin/org/rooftop/netx/redis/RedisContainer.kt
index 29e5625..8329778 100644
--- a/src/test/kotlin/org/rooftop/netx/redis/RedisContainer.kt
+++ b/src/test/kotlin/org/rooftop/netx/redis/RedisContainer.kt
@@ -1,5 +1,6 @@
package org.rooftop.netx.redis
+import io.mockk.InternalPlatformDsl.toStr
import org.springframework.boot.test.context.TestConfiguration
import org.testcontainers.containers.GenericContainer
import org.testcontainers.utility.DockerImageName
@@ -16,5 +17,9 @@ class RedisContainer {
"netx.port",
redis.getMappedPort(6379).toString()
)
+ System.setProperty(
+ "netx.undo.port",
+ redis.getMappedPort(6379).toString()
+ )
}
}
diff --git a/src/test/kotlin/org/rooftop/netx/redis/RedisStreamTransactionManagerTest.kt b/src/test/kotlin/org/rooftop/netx/redis/RedisStreamTransactionManagerTest.kt
index 5272ee1..6f1c9f7 100644
--- a/src/test/kotlin/org/rooftop/netx/redis/RedisStreamTransactionManagerTest.kt
+++ b/src/test/kotlin/org/rooftop/netx/redis/RedisStreamTransactionManagerTest.kt
@@ -10,6 +10,7 @@ import org.springframework.test.context.TestPropertySource
import reactor.test.StepVerifier
import kotlin.time.Duration.Companion.minutes
+@AutoConfigureRedisUndo
@AutoConfigureRedisTransaction
@ContextConfiguration(
classes = [
diff --git a/src/test/kotlin/org/rooftop/netx/redis/RedisUndoManagerTest.kt b/src/test/kotlin/org/rooftop/netx/redis/RedisUndoManagerTest.kt
new file mode 100644
index 0000000..fe4c287
--- /dev/null
+++ b/src/test/kotlin/org/rooftop/netx/redis/RedisUndoManagerTest.kt
@@ -0,0 +1,76 @@
+package org.rooftop.netx.redis
+
+import io.kotest.core.spec.style.DescribeSpec
+import io.kotest.matchers.shouldBe
+import org.rooftop.netx.engine.UndoManager
+import org.springframework.test.context.ContextConfiguration
+import org.springframework.test.context.TestPropertySource
+import reactor.test.StepVerifier
+
+@AutoConfigureRedisUndo
+@ContextConfiguration(classes = [RedisContainer::class])
+@TestPropertySource("classpath:application.properties")
+internal class RedisUndoManagerTest(
+ private val undoManager: UndoManager,
+) : DescribeSpec({
+
+ describe("find 메소드는") {
+ context("transactionId를 받으면,") {
+
+ val transactionId = "TX-1"
+ val undoState = "id:1"
+ undoManager.save(transactionId, undoState).block()
+
+ it("저장된 UndoState를 반환한다.") {
+ val result = undoManager.find(transactionId)
+
+ StepVerifier.create(result)
+ .assertNext {
+ it shouldBe undoState
+ }
+ .verifyComplete()
+ }
+ }
+
+ context("존재하지 않는 transactionId를 받으면,") {
+ val transactionId = "UNKNOWN_TX"
+
+ it("IllegalStateException 을 던진다.") {
+ val result = undoManager.find(transactionId)
+
+ StepVerifier.create(result)
+ .verifyErrorMessage("Cannot find undo state \"netx-group:UNKNOWN_TX\"")
+ }
+ }
+ }
+
+ describe("delete 메소드는") {
+ context("transactionId를 받으면,") {
+
+ val transactionId = "TX-2"
+ val undoState = "id:2"
+ undoManager.save(transactionId, undoState).block()
+
+ it("undoState 를 삭제하고 true를 반환한다.") {
+ val result = undoManager.delete(transactionId)
+
+ StepVerifier.create(result)
+ .expectNext(true)
+ .verifyComplete()
+ }
+ }
+
+ context("존재하지 않는 transactionId를 받으면") {
+
+ val transactionId = "UNKNOWN_TX"
+
+ it("false 를 반환한다.") {
+ val result = undoManager.delete(transactionId)
+
+ StepVerifier.create(result)
+ .expectNext(false)
+ .verifyComplete()
+ }
+ }
+ }
+})
diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties
index 8b3384d..8f5095a 100644
--- a/src/test/resources/application.properties
+++ b/src/test/resources/application.properties
@@ -3,3 +3,5 @@ netx.port=6379
netx.group=netx-group
netx.node-id=1
netx.node-name=netx-node
+netx.undo.host=localhost
+netx.undo.port=6379
From 0cd9c2440b88a7d524deaa6aead7a9f2e21af99c Mon Sep 17 00:00:00 2001
From: devxb
Date: Tue, 6 Feb 2024 15:36:34 +0900
Subject: [PATCH 2/2] =?UTF-8?q?refactor:=202=EA=B0=9C=EB=A1=9C=20=EB=B6=84?=
=?UTF-8?q?=EB=A6=AC=EB=90=9C=20AutoConfigure=20=EC=96=B4=EB=85=B8?=
=?UTF-8?q?=ED=85=8C=EC=9D=B4=EC=85=98=EC=9D=84=20=ED=95=98=EB=82=98?=
=?UTF-8?q?=EB=A1=9C=20=ED=95=A9=EC=B9=9C=EB=8B=A4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 6 ++++--
gradle.properties | 2 +-
.../autoconfig/AutoConfigureDistributedTransaction.kt | 10 ++++++++++
.../rooftop/netx/redis/RedisTransactionConfigurer.kt | 6 ++++++
.../org/rooftop/netx/redis/RedisUndoConfigurer.kt | 4 ++++
src/main/resources/META-INF/spring/spring.factories | 5 -----
.../netx/redis/RedisStreamTransactionManagerTest.kt | 4 ++--
.../org/rooftop/netx/redis/RedisUndoManagerTest.kt | 3 ++-
src/test/resources/application.properties | 2 ++
9 files changed, 31 insertions(+), 11 deletions(-)
create mode 100644 src/main/kotlin/org/rooftop/netx/autoconfig/AutoConfigureDistributedTransaction.kt
delete mode 100644 src/main/resources/META-INF/spring/spring.factories
diff --git a/README.md b/README.md
index c8f98d0..954c942 100644
--- a/README.md
+++ b/README.md
@@ -41,13 +41,15 @@ class Application {
| key | example | description |
|--------------------|-----------|------------------------------------------------------------------------------------------------------------------------------------|
+| **netx.mode** | redis | 트랜잭션 관리에 사용할 메시지 큐 구현체의 mode 입니다. |
| **netx.host** | localhost | 트랜잭션 관리에 사용할 메시지 큐 의 host url 입니다. (ex. redis host) |
| **netx.port** | 6379 | 트랜잭션 관리에 사용할 메시지 큐의 port 입니다. |
| **netx.group** | pay-group | 분산 노드의 그룹입니다. 트랜잭션 이벤트는 같은 그룹내 하나의 노드로만 전송됩니다. |
| **netx.node-id** | 1 | id 생성에 사용될 식별자입니다. 모든 서버는 반드시 다른 id를 할당받아야 하며, 1~256 만큼의 id를 설정할 수 있습니다. _`중복된 id 생성을 방지하기위해 twitter snowflake 알고리즘으로 id를 생성합니다.`_ |
| **netx.node-name** | pay-1 | _`$netx.group`_ 에 참여할 서버의 이름입니다. 같은 그룹내에 중복된 이름이 존재하면 안됩니다. |
-| **netx.undo.host** | localhost | 트랜잭션 관리에 사용할 메시지 큐 의 host url 입니다. (ex. redis host) |
-| **netx.undo.port** | 6380 | 트랜잭션 관리에 사용할 메시지 큐의 port 입니다. |
+| **netx.undo.mode** | redis | 트랜잭션 undo 상태 저장에 사용할 저장소 구현체의 mode 입니다. |
+| **netx.undo.host** | localhost | 트랜잭션 undo 상태 저장에 사용할 저장소의 host url 입니다. |
+| **netx.undo.port** | 6380 | 트랜잭션 undo 상태 저장에 사용할 저장소의 port 입니다. |
### Usage example
diff --git a/gradle.properties b/gradle.properties
index 857598c..0a8618e 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -2,7 +2,7 @@ kotlin.code.style=official
### Project ###
group=org.rooftop.netx
-version=0.1.0
+version=0.1.2
compatibility=17
### Protobuf ###
diff --git a/src/main/kotlin/org/rooftop/netx/autoconfig/AutoConfigureDistributedTransaction.kt b/src/main/kotlin/org/rooftop/netx/autoconfig/AutoConfigureDistributedTransaction.kt
new file mode 100644
index 0000000..093beb9
--- /dev/null
+++ b/src/main/kotlin/org/rooftop/netx/autoconfig/AutoConfigureDistributedTransaction.kt
@@ -0,0 +1,10 @@
+package org.rooftop.netx.autoconfig
+
+import org.rooftop.netx.redis.RedisTransactionConfigurer
+import org.rooftop.netx.redis.RedisUndoConfigurer
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration
+
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+@ImportAutoConfiguration(RedisUndoConfigurer::class, RedisTransactionConfigurer::class)
+annotation class AutoConfigureDistributedTransaction
diff --git a/src/main/kotlin/org/rooftop/netx/redis/RedisTransactionConfigurer.kt b/src/main/kotlin/org/rooftop/netx/redis/RedisTransactionConfigurer.kt
index 272409c..376852f 100644
--- a/src/main/kotlin/org/rooftop/netx/redis/RedisTransactionConfigurer.kt
+++ b/src/main/kotlin/org/rooftop/netx/redis/RedisTransactionConfigurer.kt
@@ -4,6 +4,7 @@ import org.rooftop.netx.api.TransactionManager
import org.rooftop.netx.engine.UndoManager
import org.rooftop.pay.infra.transaction.ByteArrayRedisSerializer
import org.springframework.beans.factory.annotation.Value
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.context.ApplicationEventPublisher
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@@ -25,6 +26,7 @@ class RedisTransactionConfigurer(
) {
@Bean
+ @ConditionalOnProperty(prefix = "netx", name = ["mode"], havingValue = "redis")
fun redisStreamTransactionManager(): TransactionManager =
RedisStreamTransactionManager(
nodeId,
@@ -35,6 +37,7 @@ class RedisTransactionConfigurer(
)
@Bean
+ @ConditionalOnProperty(prefix = "netx", name = ["mode"], havingValue = "redis")
fun redisStreamTransactionDispatcher(): RedisStreamTransactionDispatcher =
RedisStreamTransactionDispatcher(
applicationEventPublisher,
@@ -46,6 +49,7 @@ class RedisTransactionConfigurer(
)
@Bean
+ @ConditionalOnProperty(prefix = "netx", name = ["mode"], havingValue = "redis")
fun reactiveRedisTemplate(): ReactiveRedisTemplate {
val builder = RedisSerializationContext.newSerializationContext(
StringRedisSerializer()
@@ -57,11 +61,13 @@ class RedisTransactionConfigurer(
}
@Bean
+ @ConditionalOnProperty(prefix = "netx", name = ["mode"], havingValue = "redis")
fun byteArrayRedisSerializer(): ByteArrayRedisSerializer {
return ByteArrayRedisSerializer()
}
@Bean
+ @ConditionalOnProperty(prefix = "netx", name = ["mode"], havingValue = "redis")
fun reactiveRedisConnectionFactory(): ReactiveRedisConnectionFactory {
val port: String = System.getProperty("netx.port") ?: port
diff --git a/src/main/kotlin/org/rooftop/netx/redis/RedisUndoConfigurer.kt b/src/main/kotlin/org/rooftop/netx/redis/RedisUndoConfigurer.kt
index 603770c..5d807b2 100644
--- a/src/main/kotlin/org/rooftop/netx/redis/RedisUndoConfigurer.kt
+++ b/src/main/kotlin/org/rooftop/netx/redis/RedisUndoConfigurer.kt
@@ -1,6 +1,7 @@
package org.rooftop.netx.redis
import org.springframework.beans.factory.annotation.Value
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory
@@ -17,9 +18,11 @@ class RedisUndoConfigurer(
) {
@Bean
+ @ConditionalOnProperty(prefix = "netx.undo", name = ["mode"], havingValue = "redis")
fun redisUndoManager(): RedisUndoManager = RedisUndoManager(group, redisUndoServer())
@Bean
+ @ConditionalOnProperty(prefix = "netx.undo", name = ["mode"], havingValue = "redis")
fun redisUndoServer(): ReactiveRedisTemplate {
val stringRedisSerializer = StringRedisSerializer()
@@ -32,6 +35,7 @@ class RedisUndoConfigurer(
}
@Bean
+ @ConditionalOnProperty(prefix = "netx.undo", name = ["mode"], havingValue = "redis")
fun undoServerConnectionFactory(): ReactiveRedisConnectionFactory {
val undoServerPort: String = System.getProperty("netx.undo.port") ?: netxUndoPort
diff --git a/src/main/resources/META-INF/spring/spring.factories b/src/main/resources/META-INF/spring/spring.factories
deleted file mode 100644
index 0a97812..0000000
--- a/src/main/resources/META-INF/spring/spring.factories
+++ /dev/null
@@ -1,5 +0,0 @@
-org.rooftop.netx.redis.AutoConfigureRedisTransaction=\
-org.rooftop.netx.redis.RedisTransactionConfigurer
-
-org.rooftop.netx.redis.AutoConfigureRedisUndo=\
-org.rooftop.netx.redis.RedisUndoConfigurer
diff --git a/src/test/kotlin/org/rooftop/netx/redis/RedisStreamTransactionManagerTest.kt b/src/test/kotlin/org/rooftop/netx/redis/RedisStreamTransactionManagerTest.kt
index 6f1c9f7..a81ecef 100644
--- a/src/test/kotlin/org/rooftop/netx/redis/RedisStreamTransactionManagerTest.kt
+++ b/src/test/kotlin/org/rooftop/netx/redis/RedisStreamTransactionManagerTest.kt
@@ -5,13 +5,13 @@ import io.kotest.core.annotation.DisplayName
import io.kotest.core.spec.style.DescribeSpec
import io.kotest.matchers.shouldBe
import org.rooftop.netx.api.*
+import org.rooftop.netx.autoconfig.AutoConfigureDistributedTransaction
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.TestPropertySource
import reactor.test.StepVerifier
import kotlin.time.Duration.Companion.minutes
-@AutoConfigureRedisUndo
-@AutoConfigureRedisTransaction
+@AutoConfigureDistributedTransaction
@ContextConfiguration(
classes = [
EventCapture::class,
diff --git a/src/test/kotlin/org/rooftop/netx/redis/RedisUndoManagerTest.kt b/src/test/kotlin/org/rooftop/netx/redis/RedisUndoManagerTest.kt
index fe4c287..268f976 100644
--- a/src/test/kotlin/org/rooftop/netx/redis/RedisUndoManagerTest.kt
+++ b/src/test/kotlin/org/rooftop/netx/redis/RedisUndoManagerTest.kt
@@ -2,12 +2,13 @@ package org.rooftop.netx.redis
import io.kotest.core.spec.style.DescribeSpec
import io.kotest.matchers.shouldBe
+import org.rooftop.netx.autoconfig.AutoConfigureDistributedTransaction
import org.rooftop.netx.engine.UndoManager
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.TestPropertySource
import reactor.test.StepVerifier
-@AutoConfigureRedisUndo
+@AutoConfigureDistributedTransaction
@ContextConfiguration(classes = [RedisContainer::class])
@TestPropertySource("classpath:application.properties")
internal class RedisUndoManagerTest(
diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties
index 8f5095a..91f67be 100644
--- a/src/test/resources/application.properties
+++ b/src/test/resources/application.properties
@@ -1,7 +1,9 @@
+netx.mode=redis
netx.host=localhost
netx.port=6379
netx.group=netx-group
netx.node-id=1
netx.node-name=netx-node
+netx.undo.mode=redis
netx.undo.host=localhost
netx.undo.port=6379