4.0.5

AWS [Amazon] アダプターは Spring Cloud Function アプリを受け取り、AWSLambda で実行できる形式に変換します。

導入

AWS Lambda

AWS [Amazon] アダプターは Spring Cloud Function アプリを受け取り、AWSLambda で実行できる形式に変換します。

AWS Lambda をじっと見つめる方法の詳細はこのドキュメントの範囲外であるため、ユーザーは AWS と AWS Lambda にある程度精通しており、Spring が提供する付加価値を知りたいと考えています。

入門

Spring Cloud Function フレームワークのゴールの 1 つは、単純な関数アプリケーションが特定の環境で特定の方法で相互作用できるようにするために必要なインフラストラクチャ要素を提供することです。単純な関数アプリケーション(コンテキストまたは Spring)は、型 Supplier、Function、Consumer の Bean を含むアプリケーションです。つまり、AWS では、単純な関数 Bean が AWSLambda 環境で何らかの形で認識および実行される必要があることを意味します。

例を見てみましょう:

@SpringBootApplication
public class FunctionConfiguration {

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

	@Bean
	public Function<String, String> uppercase() {
		return value -> value.toUpperCase();
	}
}

これは、関数 Bean が定義された完全な Spring Boot アプリケーションを示しています。興味深いのは、これは表面上は単なる別の Boot アプリですが、AWS Adapter のコンテキストでは、完全に有効な AWSLambda アプリケーションでもあります。他のコードや構成は必要ありません。パッケージ化してデプロイするだけなので、その方法を見てみましょう。

物事を簡単にするために、ビルドおよびデプロイの準備ができたサンプルプロジェクトを提供しており、ここから [GitHub] (英語) アクセスできます。

./mvnw clean package を実行するだけで JAR ファイルを生成できます。必要なすべての maven プラグインは、適切な AWS デプロイ可能な JAR ファイルを生成するようにすでにセットアップされています。(JAR レイアウトの詳細については JAR レイアウトに関する注記を参照してください)。

次に、JAR ファイルを(AWS ダッシュボードまたは AWS CLI を介して)AWS にアップロードする必要があります。

ハンドラーについて確認するときは、一般的なリクエストハンドラーである org.springframework.cloud.function.adapter.aws.FunctionInvoker::handleRequest を指定します。

AWS deploy

以上です。この関数の場合、関数が大文字で返される文字列であることが期待されるいくつかのサンプルデータを使用して、関数を保存して実行します。

org.springframework.cloud.function.adapter.aws.FunctionInvoker は、AWS Lambda API の詳細から完全に分離することを目的とした汎用 AWS の RequestHandler 実装ですが、場合によっては、使用する特定の AWS の RequestHandler を指定することもできます。次のセクションでは、それを実現する方法について説明します。

AWS リクエストハンドラー

アダプターには、使用できる汎用リクエストハンドラーがいくつかあります。最も一般的なのは(そして「入門」セクションで使用したもの)は、AWS の RequestStreamHandler の実装である org.springframework.cloud.function.adapter.aws.FunctionInvoker です。ユーザーは他に何もする必要はなく、関数をデプロイするときに AWS ダッシュボードで「ハンドラー」として指定します。Kinesis、ストリーミングなどを含むほとんどのケースを処理します。

アプリに Function などの型の @Bean が複数ある場合は、spring.cloud.function.definition プロパティまたは環境変数を構成して使用するものを選択できます。関数は Spring Cloud FunctionCatalog から抽出されます。spring.cloud.function.definition を指定しない場合、フレームワークは、最初に Function、次に Consumer、最後に Supplier を検索する検索順序に従って、デフォルトを見つけようとします)。

AWS 関数ルーティング

Spring Cloud Function のコア機能の 1 つはルーティングです。これは、ユーザーが提供したルーティング指示に基づいて、他の機能に委譲する 1 つの特別な機能を持つ関数です。

AWS Lambda 環境では、この機能により 1 つの追加の利点が提供されます。これにより、単一の関数 (ルーティング関数) を AWS Lambda としてバインドできるため、API Gateway の単一の HTTP エンドポイントにバインドできます。最終的には 1 つの関数と 1 つのエンドポイントのみを管理し、アプリケーションの一部として使用できる多くの関数の恩恵を受けることができます。

詳細は提供されたサンプル [GitHub] (英語) で入手できますが、メンションする価値のある一般的なことはほとんどありません。

org.springframework.cloud.function.adapter.aws.FunctionInvoker は AWS Lambda としてバインドする関数を決定できないため、アプリケーションに複数の関数がある場合は常に、ルーティング機能がデフォルトで有効になります。そのため、デフォルトは RoutingFunction です。つまり、必要なことは、いくつかのメカニズムを使用して実行できるルーティング指示を提供することだけです (詳細については、サンプル [GitHub] (英語) を参照してください)。

また、AWS では環境変数の名前にドット . やハイフン `-` を使用できないため、Boot サポートを利用して、ドットをアンダースコアに、ハイフンをキャメルケースに置き換えることができます。たとえば、spring.cloud.function.definition は spring_cloud_function_definition になり、spring.cloud.function.routing-expression は spring_cloud_function_routingExpression になります。

カスタムランタイムを使用した AWS 関数ルーティング

カスタムランタイムを使用する場合、機能ルーティングは同じように機能します。必要なのは、関数の名前をハンドラーとして使用するのと同じ方法で、functionRouter を AWS ハンドラーとして指定することだけです。

JAR レイアウトに関する注記

Lambda では実行時に Spring Cloud Function Web または Stream アダプターは必要ないため、AWS に送信する JAR を作成する前に除外する必要がある場合があります。Lambda アプリケーションはシェーディングする必要がありますが、Spring Boot スタンドアロンアプリケーションはシェーディングしないため、2 つの別々の jar を使用して同じアプリを実行できます(サンプルのとおり)。サンプルアプリは、2 つの jar ファイルを作成します。1 つは Lambda にデプロイするための aws 分類子を使用し、もう 1 つは実行時に spring-cloud-function-web を含む 実行可能(シン)jar を作成します。Spring Cloud Function は、Start-Class 属性(親スターターを使用する場合は Spring Boot ツールによって追加されます)を使用して、JAR ファイルマニフェストから「メインクラス」を見つけようとします。マニフェストに Start-Class がない場合は、関数を AWS にデプロイするときに環境変数またはシステムプロパティ MAIN_CLASS を使用できます。

関数 Bean 定義を使用しておらず、Spring Boot の自動構成に依存しており、spring-boot-starter-parent に依存していない場合は、追加のトランスフォーマーを maven-shade-plugin 実行の一部として構成する必要があります。

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-shade-plugin</artifactId>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-maven-plugin</artifactId>
			<version>2.7.4</version>
		</dependency>
	</dependencies>
	<executions>
		<execution>
			<goals>
			     <goal>shade</goal>
			</goals>
			<configuration>
				<createDependencyReducedPom>false</createDependencyReducedPom>
				<shadedArtifactAttached>true</shadedArtifactAttached>
				<shadedClassifierName>aws</shadedClassifierName>
				<transformers>
					<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
						<resource>META-INF/spring.handlers</resource>
					</transformer>
					<transformer implementation="org.springframework.boot.maven.PropertiesMergingResourceTransformer">
						<resource>META-INF/spring.factories</resource>
					</transformer>
					<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
						<resource>META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports</resource>
					</transformer>
					<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
						<resource>META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports</resource>
					</transformer>
					<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
						<resource>META-INF/spring.schemas</resource>
					</transformer>
					<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
						<resource>META-INF/spring.components</resource>
					</transformer>
				</transformers>
			</configuration>
		</execution>
	</executions>
</plugin>

ビルドファイルの設定

AWS Lambda で Spring Cloud Function アプリケーションを実行するために、クラウドプラットフォームプロバイダーが提供する Maven または Gradle プラグインを利用できます。

Maven

Maven のアダプタープラグインを使用するには、プラグインの依存関係を pom.xml ファイルに追加します。

<dependencies>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-function-adapter-aws</artifactId>
	</dependency>
</dependencies>

JAR レイアウトに関する注記で指摘されているように、AWS Lambda にアップロードするには、シェーディングされた jar が必要です。そのために Maven Shade プラグイン [Apache] (英語) を使用できます。セットアップの例は上記にあります。

Spring Boot Maven プラグインを使用して、シン jar を生成できます。

<plugin>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-maven-plugin</artifactId>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot.experimental</groupId>
			<artifactId>spring-boot-thin-layout</artifactId>
			<version>${wrapper.version}</version>
		</dependency>
	</dependencies>
</plugin>

Maven を使用して AWSLambda に Spring Cloud Function アプリケーションをデプロイするためのサンプル pom.xml ファイル全体は ​ ここにあります [GitHub] (英語)

Gradle

Gradle のアダプタープラグインを使用するには、build.gradle ファイルに依存関係を追加します。

dependencies {
	compile("org.springframework.cloud:spring-cloud-function-adapter-aws:${version}")
}

JAR レイアウトに関する注記で指摘されているように、AWS Lambda にアップロードするには、シェーディングされた jar が必要です。そのために Gradle Shadow プラグイン (英語) を使用できます。

buildscript {
	dependencies {
		classpath "com.github.jengelman.gradle.plugins:shadow:${shadowPluginVersion}"
	}
}
apply plugin: 'com.github.johnrengelman.shadow'

assemble.dependsOn = [shadowJar]

import com.github.jengelman.gradle.plugins.shadow.transformers.*

shadowJar {
	classifier = 'aws'
	dependencies {
		exclude(
			dependency("org.springframework.cloud:spring-cloud-function-web:${springCloudFunctionVersion}"))
	}
	// Required for Spring
	mergeServiceFiles()
	append 'META-INF/spring.handlers'
	append 'META-INF/spring.schemas'
	append 'META-INF/spring.tooling'
	append 'META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports'
	append 'META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports'
	transform(PropertiesFileTransformer) {
		paths = ['META-INF/spring.factories']
		mergeStrategy = "append"
	}
}

Spring Boot Gradle プラグインと Spring Boot シン Gradle プラグインを使用してシン jar を生成できます。

buildscript {
	dependencies {
		classpath("org.springframework.boot.experimental:spring-boot-thin-gradle-plugin:${wrapperVersion}")
		classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
	}
}
apply plugin: 'org.springframework.boot'
apply plugin: 'org.springframework.boot.experimental.thin-launcher'
assemble.dependsOn = [thinJar]

Gradle を使用して AWSLambda に Spring Cloud Function アプリケーションをデプロイするためのサンプル build.gradle ファイル全体は ​ ここにあります [GitHub] (英語)

アップロード

spring-cloud-function-samples/function-sample-aws でサンプルをビルドし、-aws jar ファイルを Lambda にアップロードします。ハンドラーは example.Handler または org.springframework.cloud.function.adapter.aws.SpringBootStreamHandler にすることができます(Lambda はメソッド参照を受け入れますが、クラスの FQN であり、メソッド参照ではありません)。

./mvnw -U clean package

AWS コマンドラインツールを使用すると、次のようになります。

aws lambda create-function --function-name Uppercase --role arn:aws:iam::[USERID]:role/service-role/[ROLE] --zip-file fileb://function-sample-aws/target/function-sample-aws-2.0.0.BUILD-SNAPSHOT-aws.jar --handler org.springframework.cloud.function.adapter.aws.SpringBootStreamHandler --description "Spring Cloud Function Adapter Example" --runtime java8 --region us-east-1 --timeout 30 --memory-size 1024 --publish

AWS サンプルの関数の入力型は、"value" と呼ばれる単一のプロパティを持つ Foo です。テストするにはこれが必要になります。

{
  "value": "test"
}
AWS サンプルアプリは「関数」スタイルで記述されています(ApplicationContextInitializer として)。これは、Lambda での起動時に、従来の @Bean スタイルよりもはるかに高速であるため、@Beans (または @EnableAutoConfiguration)が必要ない場合は、これを選択することをお勧めします。ウォームスタートは影響を受けません。

型変換

Spring Cloud Function は、生の入力ストリームと関数によって宣言された型との間の型変換を透過的に処理しようとします。

例: 関数シグネチャーがそのような Function<Foo, Bar> である場合、受信ストリームイベントを Foo のインスタンスに変換しようとします。

イベント型が不明であるか、判別できない場合(Function<?, ?> など)、受信ストリームイベントを汎用 Map に変換しようとします。

生の入力

生の入力にアクセスしたい場合があります。この場合、必要なのは、InputStream を受け入れるように関数シグネチャーを宣言することだけです。例: Function<InputStream, ?>。この場合、変換は試行せず、生の入力を関数に直接渡します。

関数 Bean の定義

@Bean の代わりに関数 Bean 定義を使用できる場合、関数ははるかに速く開始されます。これを行うには、メインクラスを ApplicationContextInitializer<GenericApplicationContext> にし、GenericApplicationContext の registerBean() メソッドを使用して、必要なすべての Bean を作成します。フレームワークが入力型と出力型にアクセスできるようにするには、関数を型 FunctionRegistration の Bean として登録する必要があります。github に例があります(AWS サンプルはこのスタイルで書かれています)。次のようになります。

@SpringBootConfiguration
public class FuncApplication implements ApplicationContextInitializer<GenericApplicationContext> {

	public static void main(String[] args) throws Exception {
		FunctionalSpringApplication.run(FuncApplication.class, args);
	}

	public Function<Foo, Bar> function() {
		return value -> new Bar(value.uppercase()));
	}

	@Override
	public void initialize(GenericApplicationContext context) {
		context.registerBean("function", FunctionRegistration.class,
			() -> new FunctionRegistration<Function<Foo, Bar>>(function())
                .type(FunctionTypeUtils.functionType(Foo.class, Bar.class)));
	}

}

AWS コンテキスト

AWS ハンドラーの一般的な実装では、ユーザーは AWS コンテキストオブジェクトにアクセスできます。関数アプローチを使用すると、必要に応じて同じエクスペリエンスを得ることができます。フレームワークは、呼び出しごとに、その特定の呼び出しの AWS コンテキストインスタンスを含む aws-context メッセージヘッダーを追加します。アクセスする必要がある場合は、関数への入力パラメーターとして Message<YourPojo> を指定し、メッセージヘッダーから aws-context にアクセスするだけです。便宜上、AWSLambdaUtils.AWS_CONTEXT 定数を提供しています。

プラットフォーム固有の機能

HTTP および API ゲートウェイ

AWS には、メッセージのバッチ処理など、プラットフォーム固有のデータ型がいくつかあります。これは、それぞれを個別に処理するよりもはるかに効率的です。これらの型を利用するために、これらの型に依存する関数を書くことができます。または、Spring を使用して AWS 型からデータを抽出し、それを Spring Message に変換することもできます。これを行うには、関数が特定のジェネリクスハンドラー型(AWS サービスに依存)であることを AWS に伝え、型 Function<Message<S>,Message<T>> の Bean を提供します。S と T はビジネスデータ型です。型 Function の Bean が複数ある場合は、Spring Boot プロパティ function.name をターゲット Bean の名前になるように構成する必要がある場合もあります(たとえば、FUNCTION_NAME を環境変数として使用します)。

サポートされている AWS サービスとジェネリクスハンドラー型を以下に示します。

サービス AWS 型 ジェネリクスハンドラー

API ゲートウェイ

APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent

org.springframework.cloud.function.adapter.aws.SpringBootApiGatewayRequestHandler

Kinesis

KinesisEvent

org.springframework.cloud.function.adapter.aws.SpringBootKinesisEventHandler

例: API Gateway の背後にデプロイするには、AWS コマンドラインで(UI を介して) --handler org.springframework.cloud.function.adapter.aws.SpringBootApiGatewayRequestHandler を使用し、型 Function<Message<Foo>,Message<Bar>> の @Bean を定義します。Foo と Bar は POJO 型です(データは、AWS によって Jackson を使用してマーシャリングおよびアンマーシャリングされます)。

カスタムランタイム

AWS Lambda の AWSLambda カスタムランタイム [Amazon] 機能を利用することもでき、Spring Cloud Function はそれを簡単にするために必要なすべてのコンポーネントを提供します。

コードの観点からは、アプリケーションは他の Spring Cloud Function アプリケーションと同じように見えるはずです。する必要がある唯一のことは、Spring Boot アプリケーションを実行する zip/jar のルートに bootstrap スクリプトを提供することです。AWS で関数を作成するときは、「カスタムランタイム」を選択します。'bootstrap' ファイルの例を次に示します。

#!/bin/sh

cd ${LAMBDA_TASK_ROOT:-.}

java -Dspring.main.web-application-type=none -Dspring.jmx.enabled=false \
  -noverify -XX:TieredStopAtLevel=1 -Xss256K -XX:MaxMetaspaceSize=128M \
  -Djava.security.egd=file:/dev/./urandom \
  -cp .:`echo lib/*.jar | tr ' ' :` com.example.LambdaApplication

com.example.LambdaApplication は、関数 Bean を含むアプリケーションを表します。

AWS のハンドラー名を関数の名前に設定します。ここでも関数合成を使用できます(例: uppecrase|reverse)。それがほとんどすべてです。zip/jar を AWS にアップロードすると、関数はカスタムランタイムで実行されます。zip ファイルを適切に生成するように yoruPOM を構成する方法も確認できるサンプルプロジェクト [GitHub] (英語) を提供します。

関数 Bean 定義スタイルは、カスタムランタイムでも機能し、@Bean スタイルよりも高速です。カスタムランタイムは、Java ラムダの関数 Bean 実装よりもはるかに高速に起動できます。これは、実行時にロードする必要のあるクラスの数に大きく依存します。Spring はここではあまり機能しないため、たとえば、関数でプリミティブ型のみを使用し、カスタム @PostConstruct 初期化子で作業を行わないことで、コールドスタート時間を短縮できます。