springでリトライ処理を実装してみる

今回も備忘録です。

## このドキュメントについて
下記について記載していきます。
・@Retryableを利用したリトライ処理の実装
・RetryTemplateを利用したリトライ処理の実装


## 実行環境

・Java17
・springboot 3.0.2

## 実装
### @Retryableを利用したリトライ処理の実装

```java
@Retryable(retryFor= Exception.class, maxAttempts = 5, backoff = @Backoff(delay = 1000))
public ResponseEntity<String> retryTest() {
    count = count + 1;
    log.info("試行回数：" + count + "、時間：" + LocalDateTime.now());
    throw new RuntimeException("処理失敗してます");
}

@Recover
public ResponseEntity<String> recover(Exception e) {
    log.info(e.getMessage());
    return ResponseEntity.ok("リトライ後のリカバリー");
}
```
説明：
1. リトライしたいメソッドに@Retryableを付与することで、リトライが可能になる
引数部分に詳しい設定を行う。
・retryFor：リトライ対象のExceptionを設定
・maxAttempts:最大のリトライ回数を設定 
　(初回実行時も含めるので、上記例だと4回リトライすることになる)
・backoff：リトライ間隔の時間を設定できる 
　(上記例だと1秒。multiplierというのもあり、リトライする度に間隔を伸ばすことができる。)
　　
2.@Recoverをメソッドに付与すると、リトライしてもダメだった場合のリカバリーができる




### RetryTemplateを利用したリトライ処理の実装
複数箇所で同様のリトライ処理を入れたい場合は、こちらを利用するのがおすすめ。

```java
@Bean(name = "normalTemplate")
public RetryTemplate normalTemplate() {
  return RetryTemplate.
            builder()
            .maxAttempts(5)
            .fixedBackoff(3000)
            .retryOn(RuntimeException.class)
            .build();
}
```
説明：
1. リトライの設定に複雑さを求めないのであれば、RetryTemplateのbuilderでさくっと設定ができる。
1. maxAttempts：最大リトライ回数
fixedBackoff：リトライ間隔
retryOn：リトライ対象のException
3.@BeanでDIコンテナに登録して、利用したいクラスで依存性注入してあげればどこでも利用できる


<br>
もう少し複雑な設定もできる。

```java
@Configuration
public class RetryConfig {

    @Bean
    public SimpleRetryPolicy simpleRetryPolicy() {
        var simpleRetryPolicy = new SimpleRetryPolicy();
        simpleRetryPolicy.setMaxAttempts(5);
        return simpleRetryPolicy;
    }

    @Bean
    public FixedBackOffPolicy fixedBackOffPolicy() {
        var fixedBackOffPolicy = new FixedBackOffPolicy();
        fixedBackOffPolicy.setBackOffPeriod(3000);
        return fixedBackOffPolicy;
    }

    @Bean
    public ExceptionClassifierRetryPolicy exceptionClassifierRetryPolicy(SimpleRetryPolicy simpleRetryPolicy) {
        var exceptionClassifierRetryPolicy = new ExceptionClassifierRetryPolicy();
        exceptionClassifierRetryPolicy.setExceptionClassifier(set -> {
            return set instanceof RuntimeException ? simpleRetryPolicy : new NeverRetryPolicy();
        });

        return exceptionClassifierRetryPolicy;
    }

    @Bean(name = "customTemplate")
    public RetryTemplate retryTemplate(ExceptionClassifierRetryPolicy exceptionClassifierRetryPolicy, FixedBackOffPolicy fixedBackOffPolicy) {
        var retryListener = new RetryListener() {
            @Override
            public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback,
                                                  Throwable throwable) {
                if (context.getRetryCount() ==3) {
                    throw new ExampleException("retryListerからのエラー出力", 500);
                }
            }
        };

        return RetryTemplate
                .builder()
                .customPolicy(exceptionClassifierRetryPolicy)
                .customBackoff(fixedBackOffPolicy)
                .withListener(retryListener)
                .build();
    }

}
```
説明：
1.SimpleRetryPolicyやFixedBackOffPolicyを汎用的に利用できるように@Beanで登録しておく。

2.ExceptionClassifierRetryPolicyでExceptionごとにretryPolicyを設定する。
例で言うと、RuntimeExceptionならSimpleRetryPolicyで、それ以外ならリトライしないNeverRetryPolicyを設定する。

3.RetryListenerはエラーが起こった時や、成功した時に処理を入れ込むことができる
上記だと、リトライカウントが3回目の時はExampleExceptionを投げる設定になっている

4.あとはRetryTemplateのbuilderで設定してあげれば終わり

## まとめ
他システムとの通信時やできる限り失敗したくない処理の時には、必ず役に立つspringのRetry処理になるので知っておいて損はないです。

もっといい書き方とかあれば教えてほしいです。