Amazon Web Services ブログ

AWS SDK for Java 2.x がリリースされました

(この記事は 2018年11月19日に公開された AWS SDK for Java 2.x released を翻訳し、最新の更新情報 CHANGELOG.MD から、HTTP/2 関連の更新情報を抜粋し末尾にアップデート情報として追記したものです。)

AWS SDK for Java 2.x が正式に利用可能になり、プロダクション環境での使用がサポートされるようになりました。

バージョン 2.x は、1.11.x のコードベースを大幅に書き換えたものです。Java 8+ に対応した 2.x では、ノンブロッキング I/O、起動パフォーマンスの向上、自動ページ区切りなど、要望の多かった機能が追加されています。さらに、一貫性、不変性、使いやすさに焦点を当てて、SDK の多くの部分が更新されました。

バージョン 2.x への移行は、1.11.x と同じ JVM で実行できるため簡単です。これにより、製品全体を移行することなく、2.x から必要な機能を利用することができます。バージョン 2.x には、現在 1.11.x にある機能のほとんどが含まれていますが、すべてではありません。すぐにリリースしますので、お客様は、使わないかもしれない機能の実装を待たずに、新機能を使うことができます。まだ 2.x にしていない 1.11.x の機能のリストについては、この記事の最後を参照してください。

私たちは 2.x に期待していますが、新しいサービス API、新サービス、バグフィックス、セキュリティフィックスなど、1.11.x のアップデートは継続して行いますので、お客様にはご安心いただきたいと思います。

新機能

2.x では、ノンブロッキング I/O、プラグ可能な HTTP レイヤー、HTTP クライアント共有など、いくつかの新機能が追加されました。

ノンブロッキング I/O

AWS SDK for Java 2.x では、Netty 上に構築された新しいノンブロッキング SDK アーキテクチャを使用して、真のノンブロッキング I/O をサポートします。

1.11.x バージョンには、サービスクライアントの非同期バリアントがすでに存在しますが、これは同期クライアントの上にあるマネージドスレッドプールであるため、各要求には独自のスレッドが必要です。

2.x 非同期クライアントは HTTP 層までノンブロッキングになっており、少数の固定スレッドで同時実行性が高くなります。

非同期クライアントは、応答が使用可能になるまでスレッドをブロックするのではなく、応答の CompletableFuture を直ちに返します。例外は、クライアントのメソッドコールでスローされるのではなく、フューチャーを通じて配信されます。

// 環境から読み込まれたクレデンシャルと AWS Region を持つデフォルトの非同期クライアントを作成します
DynamoDbAsyncClient client = DynamoDbAsyncClient.create();

// Amazon Dynamo DB への呼び出しを開始します。終了するのを待つためのブロッキングはしません
CompletableFuture<ListTablesResponse> responseFuture = client.listTables();

// テーブル名だけを含む別の CompletableFuture に応答をマッピングします
CompletableFuture<List<String>> tableNamesFuture =
        responseFuture.thenApply(ListTablesResponse::tableNames);

// フューチャーが完了したとき (成功またはエラー)、レスポンスを処理します
CompletableFuture<List<String>> operationCompleteFuture =
        tableNamesFuture.whenComplete((tableNames, exception) -> {
    if (tableNames != null) {
        // テーブル名を出力します
        tableNames.forEach(System.out::println);
    } else {
        // エラーを処理をします
        exception.printStackTrace();
    }
});

// バックグラウンドで AWS の呼び出しが完了するのを待っている間に他の作業をすることもできますが、
// 代わりに "whenComplete" が完了するのを待つことにしましょう。
operationCompleteFuture.join();

Amazon S3 PutObject のように、ストリーミング入力を伴うその他の非同期操作は、非ストリーミング操作とは少し異なります。これらは、Reactive Streams インターフェースをアレンジした AsyncRequestBody を使用しています。これにより、AWS へのデータのストリーミングもノンブロッキング操作が保証されます。

// 環境から読み込まれたクレデンシャルと AWS Region を持つデフォルトの非同期クライアントを作成します
S3AsyncClient client = S3AsyncClient.create();

// Amazon S3 への呼び出しを開始。結果を待つためにブロッキングしません
CompletableFuture<PutObjectResponse> responseFuture =
        client.putObject(PutObjectRequest.builder()
                                         .bucket("my-bucket")
                                         .key("my-object-key")
                                         .build(),
                         AsyncRequestBody.fromFile(Paths.get("my-file.in")));

// フューチャーが完了したとき (成功またはエラー)、レスポンスを処理します
CompletableFuture<PutObjectResponse> operationCompleteFuture =
        responseFuture.whenComplete((putObjectResponse, exception) -> {
            if (putObjectResponse != null) {
                // オブジェクトのバージョンを出力します
                System.out.println(putObjectResponse.versionId());
            } else {
                // エラーを処理します
                exception.printStackTrace();
            }
        });

// バックグラウンドで AWS の呼び出しが完了するのを待っている間に他の作業をすることもできますが、
// 代わりに "whenComplete" が完了するのを待つことにしましょう。
operationCompleteFuture.join();

Amazon S3 の GetObjectのように、ストリーミング出力を伴う非同期操作には、リアクティブ・ストリーム・インターフェースを応用したAsyncResponseTransformer が使用されます。これにより、AWS からのデータのダウンロードもノンブロッキングで行われます。

// 環境から読み込まれたクレデンシャルと AWS Region を持つデフォルトの非同期クライアントを作成します
S3AsyncClient client = S3AsyncClient.create();

// Amazon S3 への呼び出しを開始。結果を待つためにブロックしません
CompletableFuture<GetObjectResponse> responseFuture =
        client.getObject(GetObjectRequest.builder()
                                         .bucket("my-bucket")
                                         .key("my-object-key")
                                         .build(),
                         AsyncResponseTransformer.toFile(Paths.get("my-file.out")));

// フューチャーが完了したとき (成功またはエラー)、レスポンスを処理します
CompletableFuture<GetObjectResponse> operationCompleteFuture =
        responseFuture.whenComplete((getObjectResponse, exception) -> {
            if (getObjectResponse != null) {
                // この時点で、S3 からのデータでファイル my-file.out が作成されているので、
                // オブジェクトのバージョンを表示してみましょう。
                System.out.println(getObjectResponse.versionId());
            } else {
                // エラーを処理します
                exception.printStackTrace();
            }
        });

// バックグラウンドで AWS の呼び出しが完了するのを待っている間に他の作業をすることもできますが、
// 代わりに "whenComplete" が完了するのを待つことにしましょう。
operationCompleteFuture.join();

自動ページネーション

可用性を最大化し、レイテンシーを最小化するために、多くの AWS API は結果を複数の「ページ」のレスポンスで分割します。1.11.x では、レスポンスの各ページにアクセスするために、複数の手動リクエストを行う必要がありました。2.x では、これを SDK が自動的に処理します。

たとえば、次の 2.x コードは、現在設定されているリージョンのすべての Dynamo DB テーブル名を出力します。

// 2.x ページネーション
DynamoDbClient client = DynamoDbClient.create();
client.listTablesPaginator()
      .tableNames()
      .forEach(System.out::println);

1.11.x では、同じ処理に大量の定型コードが必要です。

// 1.11.x ページネーション
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.defaultClient();

ListTablesRequest request = new ListTablesRequest();
ListTablesResult result;
do {
    result = client.listTables(request);
    result.getTableNames()
          .forEach(System.out::println);

    request.setExclusiveStartTableName(result.getLastEvaluatedTableName());
} while (request.getExclusiveStartTableName() != null);

プラグ可能な HTTP レイヤー

Java 1.11.x 用の AWS SDK は、AWS API を呼び出すために Apache HTTP client と緊密に結合されています。

これは一般的にうまく機能しますが、ランタイム環境に最適化されたクライアントを使用することによる利点がしばしばあります。

バージョン 2.x は Apache をデフォルトの同期 HTTP クライアントとして引き続き搭載していますが、ユースケースに適した別の実装に置き換えることができます。

最初に SDK をプロジェクトに追加してクライアントを作成したときには、デフォルトの HTTP 実装が自動的に選択されます。

// Apache HTTP クライアントを使用して同期クライアントを作成します
DynamoDbClient client = DynamoDbClient.create();

// Netty HTTP クライアントを使用して非同期クライアントを作成します
DynamoDbAsyncClient asyncClient = DynamoDbAsyncClient.create();

デフォルトが最適でない場合があります。

たとえば、起動時間が最大のレイテンシーの問題の 1 つである AWS Lambda では、Apache のスループットは高いが、起動速度が遅い HTTP クライアントではなく、JVM の軽量な URLConnection に基づく HTTP クライアントを使用できます。

バージョン 2.x には、このような URLConnection HTTP クライアントが含まれており、まず、アプリケーションの新しい依存関係として url-connection-client を追加します。Maven を使用している場合、pom.xml ファイルの新しいエントリを追加するだけです。

<!-- pom.xml -->
<project>
    ...
    <dependencies>
        ...
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>url-connection-client</artifactId>
            <version>2.1.0</version>
        </dependency>
    </dependencies>
    ...
</project>

次に、以下のいずれかの方法でサービスを構成します。

オプション 1: クライアントの作成時に使用する HTTP クライアントを指定します。

同じアプリケーションで複数の HTTP クライアントを使用するには、クライアントの作成時に使用する HTTP クライアントを指定する必要があります。

// URLConnection HTTP クライアントを使用して同期クライアントを作成します
DynamoDbClient clientWithUrlConnectionHttp =
        DynamoDbClient.builder()
                      .httpClientBuilder(UrlConnectionHttpClient.builder())
                      .build();

// Apache HTTP クライアントを使用して同期クライアントを作成します
DynamoDbClient clientWithApacheHttp =
        DynamoDbClient.builder()
                      .httpClientBuilder(ApacheHttpClient.builder())
                      .build();

オプション 2: JVM 起動時にシステムプロパティを使用してデフォルトの HTTP クライアントを変更します。

同期 HTTP クライアントの場合は、software.amazon.awssdk.http.service.impl システムプロパティを使用して、Java 起動時に HTTP クライアントを選択します。非同期の HTTP クライアントの場合はsoftware.amazon.awssdk.http.async.service.impl を使用します。

# デフォルトの同期 HTTP クライアントを URLConnectionHttpClient として指定します
java -Dsoftware.amazon.awssdk.http.service.impl=\
software.amazon.awssdk.http.urlconnection.UrlConnectionSdkHttpService \
MyService.jar

# デフォルトの同期 HTTP クライアントを ApacheHttpClient として指定します
java -Dsoftware.amazon.awssdk.http.service.impl=\
software.amazon.awssdk.http.apache.ApacheSdkHttpService \
MyService.jar

# デフォルトの非同期 HTTP クライアントを NettyNioAsyncHttpClient として指定します
java -Dsoftware.amazon.awssdk.http.async.service.impl=\
software.amazon.awssdk.http.nio.netty.NettySdkAsyncHttpService \
MyService.jar

オプション 3: Java コードでシステムプロパティを使用してデフォルトの HTTP クライアントを変更します。

JVM を起動するときに前述のシステムプロパティを指定する代わりに、実行時にシステムプロパティを指定することもできます。

クライアントを作成する前に、必ず値を指定してください。指定しないと、値は使用されません。

// デフォルトの同期 HTTP クライアントを URLConnectionHttpClient に設定します
System.setProperty("software.amazon.awssdk.http.service.impl",
                   "software.amazon.awssdk.http.urlconnection.UrlConnectionSdkHttpService");

// デフォルトの同期 HTTP クライアントを ApacheHttpClient に設定します
System.setProperty("software.amazon.awssdk.http.service.impl",
                   "software.amazon.awssdk.http.apache.ApacheSdkHttpService");

// デフォルトの非同期 HTTP クライアントを NettyNioAsyncHttpClient に設定します
System.setProperty("software.amazon.awssdk.http.async.service.impl",
                   "software.amazon.awssdk.http.nio.netty.NettySdkAsyncHttpService");

HTTP クライアント共有

SDK の 1.11.x では、各 AWS サービスクライアントインスタンスには、個別の接続とリソースを持つ独自の HTTP クライアントがありました。2.x でもこれがデフォルトですが、複数の AWS クライアント間で単一の HTTP クライアントを共有できるようになりました。これは、複数の AWS サービス間で 1 つの接続プールを共有したいリソースに制約のある環境で役立ちます。

// 複数の AWS クライアントで共有するために、最大 50 コネクションの HTTP クライアントを作成します
SdkHttpClient httpClient = ApacheHttpClient.builder()
                                           .maxConnections(50)
                                           .build();

// 共有 HTTP クライアントを使用して DynamoDB クライアントを作成します
DynamoDbClient dynamoDb = DynamoDbClient.builder()
                                        .httpClient(httpClient)
                                        .build();

// 共有 HTTP クライアントを使用して S3 クライアントを作成します
S3Client s3Client = S3Client.builder()
                            .httpClient(httpClient)
                            .build();

HTTP クライアントは単一の AWS クライアントに関連付けられなくなったため、HTTP クライアントの使用が終了したら、HTTP クライアントを自分で閉じる必要があります。

httpClient.close();

改善点

新機能に加えて、SDK のすべての API が更新され、一貫性、使いやすさ、スレッドの安全性が向上します。すべての変更点を確認するには、変更履歴を参照してください。主な改良点を以下にご紹介します。

不変性

1.11.x では多くのオブジェクトは変更可能です。つまり、マルチスレッド環境で変更されないようにするには、その使用には細心の注意が必要です。2.x では、SDK で使用されるすべてのオブジェクトは不変であり、ビルダーを使用して作成されます。これにより、他のスレッドが安全でない変異を行うかどうかを心配することなく、SDK を並行して使用できます。

// 設定ビルダーを使用して不変の SDK 設定オブジェクトを作成します
ClientOverrideConfiguration overrideConfiguration =
        ClientOverrideConfiguration.builder()
                                   .apiCallTimeout(Duration.ofSeconds(30))
                                   .apiCallAttemptTimeout(Duration.ofSeconds(10))
                                   .build();

// クライアントビルダーを使用して不変の DynamoDB クライアントを作成します
DynamoDbClient dynamoDbClient =
        DynamoDbClient.builder()
                      .overrideConfiguration(overrideConfiguration)
                      .build();

// リクエストビルダーを使用して不変の DynamoDB ListTablesRequest を作成します
ListTablesRequest request = ListTablesRequest.builder().build();

// クライアントを使用して不変の DynamoDB の ListTableResponse を取得します
ListTablesResponse response = dynamoDbClient.listTables(request);

すべてのオブジェクトは同じパターンに従います。作成するには、builder() メソッドを呼び出し、設定し、build()を呼び出します。

状況によっては、不変のオブジェクトを変更することが有益な場合があります。不変のオブジェクトを直接変更することはできませんが、2.x を使用すると、いくつかの値が変更された不変オブジェクトのコピーを簡単に作成できます。

// タイムアウトを有効にしてクライアント設定を作成します
ClientOverrideConfiguration configurationWithTimeouts =
        ClientOverrideConfiguration.builder()
                                   .apiCallTimeout(Duration.ofSeconds(30))
                                   .apiCallAttemptTimeout(Duration.ofSeconds(10))
                                   .build();

// タイムアウトを有効にして DynamoDB クライアントを作成します
DynamoDbClient clientWithTimeouts =
        DynamoDbClient.builder()
                      .overrideConfiguration(configurationWithTimeouts)
                      .build();

// タイムアウトと自動再試行を無効にした設定を作成します
ClientOverrideConfiguration configurationWithTimeoutsAndNoRetries =
        configurationWithTimeouts.toBuilder()
                                 .retryPolicy(RetryPolicy.none())
                                 .build();

// タイムアウトと自動再試行を無効にして DynamoDB クライアントを作成します
DynamoDbClient clientWithTimeoutsAndNoRetries =
        DynamoDbClient.builder()
                      .overrideConfiguration(configurationWithTimeoutsAndNoRetries)
                      .build();

不変性とビルダーはスレッドの安全性を確保し、2.x 全体でオブジェクトを作成するための一貫したパターンを提供しますが、冗長性をもたらします。このため、2.x は builder()と build() を呼び出す周りの手続きの多くを削減するオプションのラムダスタイルのメソッドを公開しています。

コードベースで使用するために、最も気に入ったものを選択してください。

// バケットを作成する 2 つの方法
S3Client s3Client = S3Client.create();

s3Client.createBucket(CreateBucketRequest.builder()
                                         .bucket("my-bucket")
                                         .build());

s3Client.createBucket(r -> r.bucket("my-bucket"));

// 設定オブジェクトを変更する 2 つの方法
ClientOverrideConfiguration configurationWithTimeouts =
        ClientOverrideConfiguration.builder()
                                   .apiCallTimeout(Duration.ofSeconds(30))
                                   .apiCallAttemptTimeout(Duration.ofSeconds(10))
                                   .build();

ClientOverrideConfiguration configuration1WithTimeoutsAndNoRetries =
        configurationWithTimeouts.toBuilder()
                                 .retryPolicy(RetryPolicy.none())
                                 .build();

ClientOverrideConfiguration configuration2WithTimeoutsAndNoRetries =
        configurationWithTimeouts.copy(c -> c.retryPolicy(RetryPolicy.none()));

リージョン

1.11.x では、AWS リージョンと、リージョンのメタデータを管理するための複数のクラスが存在します。2.x では、新しいRegionクラスにより、これらが簡素化されました。

// us-west-2(オレゴン)リージョンと通信する DynamoDB クライアントを作成します
DynamoDbClient dynamoDbClient = DynamoDbClient.builder()
                                              .region(Region.US_WEST_2)
                                              .build();

// DynamoDB がサポートされているすべてのリージョンを取得します
List<Region> dynamoDbRegions = DynamoDbClient.serviceMetadata().regions();

// 現在の SDK バージョンで認識されているすべてのリージョンを取得します
List<Region> allRegions = Region.regions();

// 現在の SDK バージョンでは不明な、新しくデプロイされたリージョンを指定します
Region someNewRegion = Region.of("us-west-42");

// このリージョンについて人間が読みやすい説明を取得します (例: US West (Oregon))
String regionDescription = Region.US_WEST_2.metadata().description();

ストリーミング API タイプの変換

1.11.x では、AWS へのデータのアップロードは入力ストリームを通じて行われ、S3 はファイルや文字列もサポートしていました。AWS に送信するには、他のデータ型をこれらのタイプのいずれかにマッピングする必要がありました。

バージョン 2.x は、さまざまなストリーミング入力タイプをサポートするように変更され、ストリーミングサービスとの通信にかかる労力を軽減します。

S3Client s3Client = S3Client.create();

PutObjectRequest request = PutObjectRequest.builder()
                                           .bucket("my-bucket")
                                           .key("my-key")
                                           .build();

s3Client.putObject(request, RequestBody.fromString("MyString"));
s3Client.putObject(request, RequestBody.fromFile(Paths.get("MyFile.in")));
s3Client.putObject(request, RequestBody.fromBytes("MyBytes".getBytes(UTF_8)));
s3Client.putObject(request, RequestBody.fromByteBuffer(ByteBuffer.wrap("MyByteBuffer".getBytes(UTF_8))));
s3Client.putObject(request, RequestBody.fromInputStream(new StringInputStream("MyStream"), 8));
s3Client.putObject(request, RequestBody.empty());

バージョン 2.x では、AWS からデータをダウンロードするときに使用できるタイプの変換も同様に拡張されています。

S3Client s3Client = S3Client.create();

GetObjectRequest request = GetObjectRequest.builder()
                                           .bucket("my-bucket")
                                           .key("my-key")
                                           .build();

s3Client.getObject(request, ResponseTransformer.toOutputStream(new ByteArrayOutputStream()));
s3Client.getObject(request, ResponseTransformer.toFile(Paths.get("MyFile.out")));
InputStream stream = s3Client.getObject(request, ResponseTransformer.toInputStream());
byte[] bytes = s3Client.getObject(request, ResponseTransformer.toBytes()).asByteArray();
String string = s3Client.getObject(request, ResponseTransformer.toBytes()).asUtf8String();
ByteBuffer byteBuffer = s3Client.getObject(request, ResponseTransformer.toBytes()).asByteBuffer();

Amazon S3 クライアント

AWS の多くのサービスに対して SDK サポートを提供するために、AWS SDK はコード生成を広範囲に利用しています。1.11.x では、S3 クライアントを除くすべてのサービスクライアントが生成されます。これにより、Java 以外の AWS SDK および IAM ポリシーが 、S3 オペレーション(DeleteBucketReplicationConfiguration など)を参照する方法と、Java AWS SDK が同じオペレーション(DeleteBucketReplication)を参照する方法の間に、しばしば不一致が生じます。これにより、IAM ポリシーの作成と他の SDK への切り替えが難しくなりました。これは、同等の文字列が必ずしも十分に文書化されていなかったためです。

2.x では、S3 は他のサービスと同様に生成され、オペレーション名、入力、出力が他の SDK および IAM のものと常に一致することを保証します。

Java 2.x 用の AWS SDK に欠けているものは何ですか

前述のように、1.11.x のすべての機能を 2.x にしたわけではありません。新機能と改善の恩恵を受ける場合は 2.x を使用し、2.x にまだない機能を利用する場合は 1.11.x を使用することをお勧めします。両方のバージョンの SDK を同時に使用できるため、各 SDK バージョンから使用する部分を選択して選択できます。

次の 1.11.x の機能は、まだ 2.x にはありません。( 2018年11月19日時点の情報です )

参考資料

AWS SDK for Java 2.x 開発者ガイドはこちらです。

1.11.x から 2.x への移行ガイドを使用して、既存の 1.11.x アプリケーションの移行に必要な内容をご確認ください。

変更点の正確なリストは、1.11.x から 2.x の更新履歴をご確認ください。

お問合せ

GitHub で 2.x ソースを確認できます。

AWS SDK for Java 2.x コミュニティチャットに Gitter で参加しましょう。

GitHub の Issues ページで、機能リクエストを明確にしたり、既存のリクエストにアップ投票できます。

アップデート情報

(以下は、翻訳者が AWS SDK for Java 2.x のリポジトリの CHANGELOG.MD から、HTTP/2 関連の更新情報を抜粋し翻訳したものです。ご覧のように継続的に更新を行っておりますので、最新の情報を CHANGELOG.MD から取得されることをお勧めいたします。)

2.15.35 2020-11-24

Amazon Transcribe Streaming Service に関する機能
Amazon Transcribe Medicalストリーミングは、医療専門分野と HTTP/2 のサポートを追加しました。Amazon Transcribe ストリーミングは、追加の言語をサポートしています。ストリーミングのコーデックとして、OGG/OPUS と FLAC をサポートしました。

2.13.25 2020-05-27

Elastic Load Balancing に関する機能
このリリースでは、Network Load Balancer の HTTP/2 ALPN プリファレンスリストのサポートが追加されました。

2.10.39 2019-12-19

Netty NIO HTTP Client に関する機能
NettyNioAsyncHttpClient.Builder#http2Configuration(Http2Configuration) とともにHttp2Configuration#initialWindowSize(Integer)を使用して、Nettyクライアントが開く HTTP/2 コネクションで SETTINGS_INITIAL_WINDOW_SIZE を設定できるようになりました。詳細は https://tools.ietf.org/html/rfc7540#section-6.5.2 を参照してください。

2.10.31 2019-12-09

Netty NIO HTTP Client に関する機能
HTTP/2 接続が 5 秒間 ゼロストリームだった場合、接続を閉じじます。これは、useIdleConnectionReaper(false)で無効にしたり、NettyNioAsyncHttpClient.BuilderconnectionMaxIdleTime(...)で期間を調整することができます。
HTTP/2 接続に定期的に ping を行い、サービスが応答しない場合は接続を閉じます。ping の周期とタイムアウト時間は現在設定できません。

2.10.0 2019-10-24

AWS App Mesh に関する機能
gRPC および HTTP/2 プロトコルのサポートが追加されました。

この記事の翻訳は テクニカルソリューション部 ソリューションアーキテクト の宮島 嶺が担当しました。原文はこちらです。