Spring Boot 環境での AWS SDK Client 利用を簡単便利に

Spring Boot 環境で AWS SDK for Java を利用するにあたって、開発にも運用にも便利なライブラリを作りました。ご賞味ください。

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

永遠の生魚おじさん、都元です。今年はチーム向けの設計開発ガイドライン都元ダイスケの五輪書を書いたりしました。が、そう言えば宮本武蔵の五輪書を読んだことねぇなぁ…と思って読んでみたところ、最終巻空の巻のコンテンツ量がほとんどありませんでしたw 本家と違って未完の書にならないように、頑張りたいと思いました。

さて、弊社は本日を最終営業日として、これから冬季休業となります。 今年も一年、どうもありがとうございました。というわけで書き納め三本締めの一本目。

Spring 環境での AWS SDK Client

通常、Spring 環境で AWS SDK for Java のクライアントを利用する場合は、各クライアントを bean として利用するために、次のように Java configuration を記述します。

  @Bean
  public AmazonS3 amazonS3() {
    return AmazonS3ClientBuilder.defaultClient();
  }

各クライアントには、個別に connectionTimeoutsocketTimeout をはじめ、様々な設定値を持っています。上記の amazonS3 は、コードを修正して再ビルドをしなければ、設定ができません。

  @Bean
  public AmazonSQS amazonSQS() {
    ClientConfiguration clientConfig = new ClientConfiguration()
        .withConnectionTimeout(2500)
        .withSocketTimeout(25000);
    return AmazonSQSClientBuilder.standard()
        .withClientConfiguration(clientConfig)
        .build();
  }

上記の amazonSQS ではタイムアウト値を設定していますが、これもハードコードしてある値であるため、コードを修正して再ビルドをしなければ、設定値を変えられません。

  @Bean
  public AmazonSNS amazonSNS(
      @Value("aws.sns.endpoint.service-endpoint") String serviceEndpoint,
      @Value("aws.sns.endpoint.signing-region") String signingRegion) {
    EndpointConfiguration endpointConfig = new EndpointConfiguration(serviceEndpoint, signingRegion);
    return AmazonSNSClientBuilder.standard()
        .withEndpointConfiguration(endpointConfig)
        .build();
  }

この amazonSNS では、API endpoint URL をモック (localstack 等) に切り替えることはできますが、タイムアウトの設定等はできません。

  @Bean
  public AmazonDynamoDB amazonDynamoDB(@Value("dynamodb.region") String region) {
    return AmazonDynamoDBClientBuilder.standard()
        .withRegion(region)
        .build();
  }

dynamoDB も、リージョンの設定はできますが、そのプロパティ命名法に一貫性がありません。SNS クライアントの規則に従えば、aws.dynamodb.region あたりが妥当でしょうか?

  @Bean
  public S3Client s3Client() {
    return S3Client.create(); // AWS SDK for Java v2!!
  }

s3ClientAWS SDK for Java 2.0 の実装ですが、これってどうやって設定したらいいんでしたっけ?

などなど…。素朴にクライアントを定義すると、このような問題を抱えてしまいます。

設定の外部化機能は、運用のための機能

できることならば、様々な設定を再ビルドすることなく、統一感のあるプロパティとしてコントロールしたいですよね。万一の時にアプリケーションの各種設定を、開発担当者ではなく運用担当者がコントロールしやすいことには、一定のメリットがあります。

そもそも「再ビルドすることなく、外部から設定を変更できる」機能を Spring Boot では設定の外部化 (Externalized Configuration) と呼びます。

Spring Boot の Externalized Configuration は、設定値を次の優先順位 (抜粋) で探していき、最初に見つかった値をアプリケーション内で参照できる仕組みです。

  1. テストに記述してある設定
  2. $ java -jar foobar.jar で起動したときのコマンドライン引数
  3. Java システムプロパティ
  4. OS 環境変数
  5. クラスパス内にある application-{profile}.properties ファイル
  6. クラスパス内にある application.properties ファイル

1 は少し例外的だとしても、運用担当者が触りやすい 2〜4 の優先順位が高くなっているのがポイントです。開発担当者は 5〜6 の仕組みを使ってデフォルト値を定義しておくことになります。

では AWS SDK Client の設定外部化は、どうすればいい?

自ら体系的なルールを作って、1つ1つ定義していくのは大変です。ということで、ライブラリを作りました。

aws-client-spring-boot-configuration - GitHub

ビルド成果物 (jar) は bintry 経由で jcenter に上げてありますので、Gradle では次のように参照できます。

repositories {
  jcenter()
}
dependencies {
  implementation 'jp.xet.spring.aws:aws-client-spring-boot-configuration:1.0.0'
}

このライブラリを使えば、上に挙げたすべてのクライアント bean 定義は次のとおりで完了します。

@Configuration
@EnableAwsClientV1({
  AmazonS3.class,
  AmazonSQS.class,
  AmazonSNS.class,
  AmazonDynamoDB.class
})
@EnableAwsClientV2(S3Client.class)
public class AwsClientConfiguration {
}

さらに、クラスパス内の application.properties に次の内容を記述しておきましょう。

aws1.sqs.client.connection-timeout=2500 aws1.sqs.client.socket-timeout=25000

これで、運用担当者は環境変数として AWS1_SQS_CLIENT_CONNECTION_TIMEOUT=5000 を定義すれば SQS クライアントのコネクションタイムアウトを 5 秒に変更できる、というわけです。

サンプルアプリケーション

ココ にある内容と重複しますが。

@Slf4j
@RequiredArgsConstructor
@SpringBootApplication
@EnableAwsClientV1(AmazonEC2.class)
public class TestApplication implements CommandLineRunner {

  public static void main(String[] args) {
    SpringApplication.run(TestApplication.class, args);
  }

  private final AmazonEC2 ec2;

  @Override
  public void run(String... args) throws Exception {
    ec2.describeRegions().getRegions().stream()
      .map(Region::getRegionName)
      .forEach(log::info);
  }
}

EC2 の DescribeRegions アクションを実行し、すべてのリージョン名 (us-east-1 など) をログ出力するものです。この程度ではあまり意味はないと思いますが、環境変数等によってこの辺の設定がコントロールできます。

ちなみに、AWS SDK for Java v2 だとこんな感じになるようです。ほとんど変わらないですね。間違い探しに近い。

@Slf4j
@RequiredArgsConstructor
@SpringBootApplication
@EnableAwsClientV2(Ec2Client.class)
static class TestApplication implements CommandLineRunner {

  public static void main(String[] args) {
    SpringApplication.run(TestApplication.class, args);
  }


  private final Ec2Client ec2;


  @Override
  public void run(String... args) throws Exception {
    ec2.describeRegions().regions().stream()
      .map(Region::regionName)
      .forEach(log::info);
  }
}

まとめ

ドキュメントは英語を軸としましたが日本語版も書きました。ぜひご活用ください。

この仕組みを使って、AWS の各サービス検証作業も気軽に進めていけたらなと思っています。

みなさま、良いお年をお迎えください。