Spring Retry
概要
Kotlin言語を使って、Spring Retryを活用する簡単なプロジェクトを作成してみる。
Spring Retryは、メソッドを呼び出して例外が発生したとき、指定したメソッドを自動的に再呼び出しする機能を提供する。
一時的なネットワーク接続障害が発生したときのFailoverに有用である。
Failover: システムの代替動作。
普段使用するサーバーとそのクローンサーバーを用意しておき、使用中のサーバーが障害により利用しづらくなった場合、クローンサーバーにその処理を代行させ、無停止システムを構築することを意味する。
プロジェクト作成
次のようにcurlコマンドを使って、Spring Bootの初期プロジェクトを作成する。
curl https://start.spring.io/starter.tgz \
-d bootVersion=2.7.2 \
-d baseDir=spring-retry \
-d groupId=com.devkuma \
-d artifactId=spring-retry \
-d packageName=com.devkuma.retry \
-d applicationName=RetryApplication \
-d packaging=jar \
-d language=kotlin \
-d javaVersion=11 \
-d type=gradle-project | tar -xzvf -
このコマンドを実行すると、Java 11、Spring Boot 2.7.2のWebプロジェクトが作成される。
Retry設定
ビルドスクリプト
Retryを動作させるためのライブラリを、ビルドスクリプトに次のように追加する。
/build.gradle.kts
dependencies {
// ... 中略 ...
implementation("org.springframework.retry:spring-retry:1.3.3")
implementation("org.springframework:spring-aspects:5.3.22")
implementation("io.github.microutils:kotlin-logging:2.1.23")
// ... 中略 ...
}
- spring-retry: Retryライブラリ
- spring-aspects: AOP関連ライブラリ
- kotlin-logging: 動作確認用のログライブラリ
Retry設定クラスの追加
新規にRetryConfig設定クラスを作成し、次のように記述する。
/src/main/kotlin/com/devkuma/retry/config/RetryConfig.kt
package com.devkuma.retry.config
import org.springframework.context.annotation.Configuration
import org.springframework.retry.annotation.EnableRetry
@EnableRetry
@Configuration
class RetryConfig
Retry適用関連クラスの作成
Retryを適用したServiceインターフェースとクラスの追加
/src/main/kotlin/com/devkuma/retry/service/RetryService.kt
package com.devkuma.retry.service
import org.springframework.retry.annotation.Backoff
import org.springframework.retry.annotation.Retryable
import java.sql.SQLException
interface RetryService {
@Retryable(value = [SQLException::class], maxAttempts = 2, backoff = Backoff(delay = 1000))
fun retry(error: Boolean) : String
fun templateRetry(error: Boolean) : String
}
/src/main/kotlin/com/devkuma/retry/service/impl/RetryServiceImpl.kt
package com.devkuma.retry.service.impl
import com.devkuma.retry.service.RetryService
import mu.KotlinLogging
import org.springframework.retry.annotation.Recover
import java.sql.SQLException
private val log = KotlinLogging.logger {}
class RetryServiceImpl : RetryService {
override fun retry(error: Boolean) : String {
log.info { "Retry called. error=$error" }
if (error)
throw SQLException("retry SQLException")
return "Success"
}
@Recover
fun recover(exception: SQLException, error: Boolean) : String {
log.info { "Recover called: message=${exception.message}"}
return "Success"
}
}
@Retryableアノテーションを宣言したretry関数は、SQLExceptionが発生したときに再試行する。最大2回の再試行を1000ミリ秒間隔で実行する。 2回試しても成功しない場合は、@Recoverアノテーションを設定したrecover関数が実行される。retry関数は、引数変数errorがtrueならエラーが発生し、errorがfalseならエラーが発生しない。
Retryテストコードで確認
Retryテストコードクラスの追加
動作確認のため、次のようにテストコードを作成する。
/src/test/kotlin/com/devkuma/retry/service/RetryServiceTest.kt
package com.devkuma.retry.service
import com.devkuma.retry.config.RetryConfig
import com.devkuma.retry.service.impl.RetryServiceImpl
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
@SpringBootTest(
classes = [
RetryServiceImpl::class,
RetryConfig::class
]
)
class RetryServiceTest {
@Autowired
private lateinit var retryService: RetryService
@Test
fun `retry error=true`(){
// Given
// When
var result = retryService.retry(true)
// Then
Assertions.assertEquals("Success", result)
}
@Test
fun `retry error=false`(){
var result = retryService.retry(false)
Assertions.assertEquals("Success", result)
}
}
Retryテストコード実行確認
作成したテストコードを実行すると、次のように動作する。
Retryエラーが発生した場合
retry error=true関数が呼び出されたときの結果は次のとおりである。
2022-08-11 18:20:55.787 INFO 13102 --- [ Test worker] c.d.retry.service.impl.RetryServiceImpl : Retry called. param error=true
2022-08-11 18:20:56.792 INFO 13102 --- [ Test worker] c.d.retry.service.impl.RetryServiceImpl : Retry called. param error=true
2022-08-11 18:20:56.792 INFO 13102 --- [ Test worker] c.d.retry.service.impl.RetryServiceImpl : Recover called: message=retry SQLException, param error=true
Retryエラーが発生しなかった場合
retry error=false関数が呼び出されたときの結果は次のとおりである。
2022-08-11 18:20:56.801 INFO 13102 --- [ Test worker] c.d.retry.service.impl.RetryServiceImpl : Retry called. param error=false
RetryTemplate設定
RetryOperationsインターフェース
Spring Retryは、一連のexcute()メソッドを提供するRetryOperationsインターフェースを提供する。
package org.springframework.retry;
import org.springframework.retry.support.DefaultRetryState;
public interface RetryOperations {
// ... 中略 ...
<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RecoveryCallback<T> recoveryCallback)
throws E;
// ... 中略 ...
}
execute()のパラメータであるRetryCallbackは、失敗時に再試行すべきビジネスロジックを挿入するためのインターフェースである。
package org.springframework.retry;
public interface RetryCallback<T, E extends Throwable> {
T doWithRetry(RetryContext context) throws E;
}
再試行がすべて失敗すると、RetryOperationsはRecoveryCallbackを呼び出す。この機能を使うには、クライアントはexecute()メソッドを呼び出すときにRecoveryCallbackオブジェクトを渡す必要がある。
package org.springframework.retry;
public interface RecoveryCallback<T> {
T recover(RetryContext context) throws Exception;
}
RetryOperations実装
RetryTemplateはRetryOperationsの実装である。@ConfigurationクラスでRetryTemplate Beanを構成してみる。
/src/main/kotlin/com/devkuma/retry/config/RetryConfig.kt
package com.devkuma.retry.config
import com.devkuma.retry.support.DefaultListenerSupport
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.retry.annotation.EnableRetry
import org.springframework.retry.backoff.FixedBackOffPolicy
import org.springframework.retry.policy.SimpleRetryPolicy
import org.springframework.retry.support.RetryTemplate
@EnableRetry
@Configuration
class RetryConfig {
@Bean
fun retryTemplate(): RetryTemplate {
val fixedBackOffPolicy = FixedBackOffPolicy()
fixedBackOffPolicy.backOffPeriod = 2000L
val retryPolicy = SimpleRetryPolicy()
retryPolicy.maxAttempts = 3
val retryTemplate = RetryTemplate()
retryTemplate.setBackOffPolicy(fixedBackOffPolicy)
retryTemplate.setRetryPolicy(retryPolicy)
retryTemplate.registerListener(DefaultListenerSupport())
return retryTemplate
}
}
RetryPolicyは処理の再試行回数を設定する。SimpleRetryPolicyは固定回数だけ再試行するために使われる。BackOffPolicyは再試行時のバックオフを制御するために使われる。FixedBackOffPolicyは、続行前に一定時間停止する。
RetryTemplateテストコードで確認
RetryTemplateテストクラスの作成
既存のテストコードにtemplateRetryだけを追加した。
/src/test/kotlin/com/devkuma/retry/service/RetryServiceTest.kt
package com.devkuma.retry.service
// ... 中略 ...
class RetryServiceTest {
@Autowired
private lateinit var retryService: RetryService
@Autowired
private lateinit var retryTemplate: RetryTemplate
// ... 中略 ...
@Test
fun templateRetry() {
retryTemplate.execute<Any, SQLException>(
{ retryService.templateRetry(true) },
{ retryService.templateRetry(false) }
)
}
}
RetryTemplateテスト実行確認
TemplateRetryでエラーが発生しなかった場合
templateRetry関数が呼び出されたときの結果は次のとおりである。
2022-08-11 18:21:38.192 INFO 13197 --- [ Test worker] c.d.r.support.DefaultListenerSupport : onOpen
2022-08-11 18:21:41.384 INFO 13197 --- [ Test worker] c.d.retry.service.impl.RetryServiceImpl : Retry called. param error=true
2022-08-11 18:21:42.389 INFO 13197 --- [ Test worker] c.d.retry.service.impl.RetryServiceImpl : Retry called. param error=true
2022-08-11 18:21:42.398 INFO 13197 --- [ Test worker] c.d.retry.service.impl.RetryServiceImpl : Recover called: message=retry SQLException, param error=true
2022-08-11 18:21:44.146 INFO 13197 --- [ Test worker] c.d.r.support.DefaultListenerSupport : onClose
参考
- Spring Retry | Java Development Journal
- Guide to Spring Retry | Baeldung
- Spring Retry Review - 아빠프로그래머의 좌충우돌 개발하기!
- spring-projects/spring-retry | GitHub
上記のサンプルコードはGitHubで確認できる。