【AWS SDK for Java】S3クライアントにリトライポリシーを設定する

ジャストほしい情報が日本語でなかったのでドキュメンテーションする。
AWS SDK for Javaを利用してS3にファイルをアップロードする際に、やや詳細な設定を付加する。

# 目次
- リトライ周りのケース概略
- とりあえず実装してみる
- 関連クラスの整理
- その他
- 参考

# リトライ周りのケース概略
AWS SDK for Javaを使って、ローカルにあるS3にファイルをアップロードしたい。
そして、AWS側の都合で通信断となったり諸事情により正常稼働できないケースを見越して、AWSクライアントにリトライポリシーを仕込みたい。
今回は、AWS SDK for Javaに含まれる `AmazonS3` オブジェクトのシングルトンを返すユーティリティクラスを用意する。

なお、本記事では自分が特に困った「クライアント生成時のオプション設定」にフォーカスを置く。そのため、ファイルアップロード前後の処理関連の説明は省略する。

# とりあえず実装してみる
シンプルなクライアントオプションを詰めたクライアントを構築する。
流れとしては、リトライポリシーインスタンス、クライアント設定インスタンス、そしてクライアントの3つを準備していく。

まずは、ポリシーの基本設定やAWS定義のストラテジーなどを詰めた `RetryPolicy` インスタンスを返すメソッドを用意。

```java:AmazonS3Utils.java
/**
 * ExponentialBackoffStrategyのコンストラクタにbaseDelayとmaxBackoffTimeの適切な値を入れる。
 * 注意：ここではサンプルとして適当な値を入れてます。
 */
private static RetryPolicy getS3BaseRetryPolicy() {
        return new RetryPolicy(
                new PredefinedRetryPolicies.SDKDefaultRetryCondition(),
                new PredefinedBackoffStrategies.ExponentialBackoffStrategy(0, 0),
                PredefinedRetryPolicies.DEFAULT_MAX_ERROR_RETRY,
                true
        );
    }
```

次に、↑で定義した`RetryPolicy`インスタンスを返すメソッドを呼んで、クライアント設定を定義する。

```java:AmazonS3Utils.java
private static final ClientConfiguration clientConfiguration = new ClientConfiguration()
            .withRetryPolicy(getS3BaseRetryPolicy());
```

そして、他のクラスやメソッドが呼び出すS3クライアントのエントリーポイントとなる `AmazonS3` オブジェクトを、クライアント設定を詰めて初期化。

```java:AmazonS3Utils.java
private static final AmazonS3 amazonS3 = AmazonS3ClientBuilder
            .standard()
            .withClientConfiguration(clientConfiguration)
            .withRegion(Regions.DEFAULT_REGION)
            .build();
```

ここまでの設定をまとめると、


```java:AmazonS3Utils.java
public final class AmazonS3Utils {

    /**
     * リトライポリシーを反映した、クライアント設定を初期化。
     */
    private static final ClientConfiguration clientConfiguration = new ClientConfiguration()
            .withRetryPolicy(getS3BaseRetryPolicy());

    /**
     * AmazonS3インスタンスを初期化。
     */
    private static final AmazonS3 amazonS3 = AmazonS3ClientBuilder
            .standard()
            .withClientConfiguration(clientConfiguration)
            .withRegion(Regions.DEFAULT_REGION).build();

    /**
     * ファイル操作を提供するオブジェクトを返す
     */
    public static AmazonS3 getS3() {
        return amazonS3;
    }

    /**
     * リトライポリシーをインスタンス化。
     */
    private static RetryPolicy getS3BaseRetryPolicy() {
        return new RetryPolicy(
                new PredefinedRetryPolicies.SDKDefaultRetryCondition(),
                new PredefinedBackoffStrategies.ExponentialBackoffStrategy(0, 0),
                PredefinedRetryPolicies.DEFAULT_MAX_ERROR_RETRY,
                true
        );
    }
}

```

これで準備完了。あとは、`getS3`メソッドで設定を反映したS3クライアントを呼び出せるようになる。


# 登場オブジェクトの整理
実装例に出てくる複数のクラスの役割を整理してみたい。
自分の調べ方が悪かったのか、やりたいことをさくっとできる感じではなかった...

## AmazonS3（インターフェース）
S3のオブジェクトを操作するメソッドを定義するインターフェース。
具体的な実装はクライアントビルダークラスにより提供されるようだ。
[AmazonS3](https://github.com/aws/aws-sdk-java/blob/master/aws-java-sdk-s3/src/main/java/com/amazonaws/services/s3/AmazonS3.java)
このオブジェクトはインターフェースであり、具は`AmazonS3ClientBuilder`などにより提供されるようだ。

もともと、リトライ周りの設定を追加する前は`AmazonS3ClientBuilder.standard().build();`で済ませていた。最小限の機能で済むなら、そのようなミニマルなクライアントで十分かと思う。

## ClientConfiguration（クラス）
[ClientConfiguration](https://github.com/aws/aws-sdk-java/blob/master/aws-java-sdk-core/src/main/java/com/amazonaws/ClientConfiguration.java)は、リトライやプロキシ、ユーザエージェント文字列などAWSクライアントに追加できるオプションの設定クラス。
今回の例では自分は多少カスタマイズを加えた`RetryPolicy`を渡したが、`PredefinedRetryPolicies`にあるstaticメソッドである`getDefaultRetryPolicy()`を呼び出すことで、デフォルト設定をクライアント設定に詰められるようだ。

## RetryPolicy（クラス）
[RetryPolicy](https://github.com/aws/aws-sdk-java/blob/master/aws-java-sdk-core/src/main/java/com/amazonaws/retry/RetryPolicy.java)は、 `ClientConfiguration` と組み合わせて、リトライポリシーを構築するクラス。
コンストラクタに必要なオブジェクトや値を詰めて初期化する。

コンストラクタには、`RetryCondition`や`BackoffStrategy`などのインターフェースが引数に指定されているのだが、引数のインターフェースに合うクラスに何を指定すればよいのか、最初悩んだ。いくつかコンストラクタがあるが、そのうちの一つを挙げてみると

```java:RetryPolicy.java
public RetryPolicy(RetryCondition retryCondition,
    BackoffStrategy backoffStrategy,
    int maxErrorRetry,
    boolean honorMaxErrorRetryInClientConfig) {
    this(retryCondition, backoffStrategy, maxErrorRetry, honorMaxErrorRetryInClientConfig, false); 
} 
```

いろいろ調べていくうちに、どうやらSDKは事前にある程度定義したポリシーを詰められるクラスを用意していたとわかった。

`RetryCondition`インターフェースには、今回の実装ではAWS SDKのデフォルトに従うことにした。[PredefinedRetryPolicies](https://github.com/aws/aws-sdk-java/blob/master/aws-java-sdk-core/src/main/java/com/amazonaws/retry/PredefinedRetryPolicies.java)に、何種類か、使えそうな実装があるので`RetryPolicy`のコンストラクタに渡せばよい。
設定にこだわりがないのであれば、デフォルト設定を返してくれるstaticプロパティ`DEFAULT_RETRY_CONDITION`を指定すればよさそう。

[BackoffStrategy](https://github.com/aws/aws-sdk-java/blob/master/aws-java-sdk-core/src/main/java/com/amazonaws/retry/RetryPolicy.java)インターフェースを拡張した抽象クラス[V2CompatibleBackoffStrategy](https://github.com/aws/aws-sdk-java/blob/master/aws-java-sdk-core/src/main/java/com/amazonaws/retry/V2CompatibleBackoffStrategy.java)が用意されており、この抽象クラスを拡張したアダプターおよび各種具象クラスを使えば`BackoffStrategy`インターフェース相当の引数を設定できる。

詳しくいうと、[PredefinedBackoffStrategies](https://github.com/aws/aws-sdk-java/blob/master/aws-java-sdk-core/src/main/java/com/amazonaws/retry/PredefinedBackoffStrategies.java)には `V2CompatibleBackoffStrategyAdapter`を拡張した、

- FullJitterBackoffStrategy
- EqualJitterBackoffStrategy
- EqualJitterBackoffStrategy

などが用意されている。RetryPolicyを初期化する際に、コンストラクタになんとかBackoffStrategyを渡すことで細かい設定を施すことが可能である。

# その他
本当は公式のドキュメントで、いい感じに説明してくれているサムシングがある気がしている...
自分は探し当てられなかったため、「そんな無理せんでもこれ読んだらええんやけど」ってのがあればご指摘いただければありがたいです。

# 参考

- [AWS でのエラー再試行とエクスポネンシャルバックオフ](https://docs.aws.amazon.com/ja_jp/general/latest/gr/api-retries.html)
- [AWS SDK Java でAPIコールをExponential Backoffでリトライする](https://dev.classmethod.jp/articles/retry-api-call-with-exponential-backoff-using-aws-sdk-java/)
- [Class RetryPolicy](https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/retry/RetryPolicy.html)
- [Class PredefinedRetryPolicies](https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/retry/PredefinedRetryPolicies.html)
- [Class ClientConfiguration](https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/ClientConfiguration.html)
- [Tuning AWS Java SDK HTTP request settings for latency-aware Amazon DynamoDB applications](https://aws.amazon.com/jp/blogs/database/tuning-aws-java-sdk-http-request-settings-for-latency-aware-amazon-dynamodb-applications/)
- [Exponential Backoff - wikipedia](https://en.wikipedia.org/wiki/Exponential_backoff)
- [大規模分散システム ”Amazon Web Services”の使い方(PDF)](http://cloud-spiral.enpit.jp/wp-content/uploads/Slides_Koji.pdf)