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 Lambda ではさまざまな RequestHandlers を実装できますが、Spring Cloud Function では何も実装する必要はなく、代わりに 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 を検索する検索順序に従って、デフォルトを見つけようとします)。

型変換

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

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

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

生の入力

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

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 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 のハンドラー名を関数の名前に設定します。ここでも関数合成を使用できます (例: uppercase|reverse)。これでほぼ完了です。zip/jar を AWS にアップロードすると、関数はカスタムランタイムで実行されます。POM を構成して zip ファイルを適切に生成する方法を確認できるサンプルプロジェクト [GitHub] (英語) も提供しています。

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

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

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

コンテナーイメージのデプロイ

カスタムランタイムは、コンテナーイメージデプロイ の処理も担当します。ここで説明されているのと同様の方法でコンテナーイメージをデプロイする場合は、環境変数 DEFAULT_HANDLER に関数の名前を忘れずに設定することが重要です。

例: 以下に示す関数 Bean の場合、DEFAULT_HANDLER 値は readMessageFromSQS になります。

@Bean
public Consumer<Message<SQSMessageEvent>> readMessageFromSQS() {
	return incomingMessage -> {..}
}

また、spring_cloud_function_web_export_enabled も false に設定されていることを忘れないでください。デフォルトです。

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 プラグイン (英語) を使用できます。

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

以下は完全な gradle ファイルです

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.2.0-M2'
	id 'io.spring.dependency-management' version '1.1.3'
	id 'com.github.johnrengelman.shadow' version '8.1.1'
	id 'maven-publish'
	id 'org.springframework.boot.experimental.thin-launcher' version "1.0.31.RELEASE"
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
	sourceCompatibility = '17'
}

repositories {
	mavenCentral()
	mavenLocal()
	maven { url 'https://repo.spring.io/milestone' }
}

ext {
	set('springCloudVersion', "2023.0.0-M1")
}

assemble.dependsOn = [thinJar, shadowJar]

publishing {
	publications {
		maven(MavenPublication) {
			from components.java
			versionMapping {
				usage('java-api') {
					fromResolutionOf('runtimeClasspath')
				}
				usage('java-runtime') {
					fromResolutionResult()
				}
			}
		}
	}
}

shadowJar.mustRunAfter thinJar


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

shadowJar {
	archiveClassifier = 'aws'
	manifest {
    	inheritFrom(project.tasks.thinJar.manifest)
  	}
  	// 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"
	}
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter'
	implementation 'org.springframework.cloud:spring-cloud-function-adapter-aws'
	implementation 'org.springframework.cloud:spring-cloud-function-context'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

dependencyManagement {
	imports {
		mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
	}
}

tasks.named('test') {
	useJUnitPlatform()
}

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