Mark Fisher, Dave Syer, Oleg Zhurakousky, Anshul Mehra, Dan Dobrin

3.1.1


導入

Spring Cloud Function は、次の高レベルのゴールを持つプロジェクトです。

  • 関数を介したビジネスロジックの実装を推進します。

  • 同じコードを Web エンドポイント、ストリームプロセッサー、タスクとして実行できるように、ビジネスロジックの開発ライフサイクルを特定のランタイムターゲットから分離します。

  • サーバーレスプロバイダー全体で統一されたプログラミングモデルをサポートし、スタンドアロン(ローカルまたは PaaS)で実行する機能をサポートします。

  • サーバーレスプロバイダーで Spring Boot 機能(自動構成、依存性注入、メトリクス)を有効にします。

すべてのトランスポートの詳細とインフラストラクチャを抽象化し、開発者が使い慣れたツールとプロセスをすべて維持し、ビジネスロジックにしっかりと集中できるようにします。

完全な実行可能かつテスト可能な Spring Boot アプリケーション(単純な文字列操作を実装)は次のとおりです。

@SpringBootApplication
public class Application {

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

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

これは単なる Spring Boot アプリケーションであるため、他の Spring Boot アプリケーションと同じように、ローカルおよび CI ビルドでビルド、実行、テストできます。Function は java.util からのものであり、Flux はプロジェクト Reactor (英語) からの Reactive Streams (英語)  Publisher です。この機能には、HTTP またはメッセージングを介してアクセスできます。

Spring Cloud Function には 4 つの主な機能があります。

簡単に言うと、Spring Cloud Function には以下のような特徴があります。FunctionConsumerSupplier 型の @Beans のラッパーで、HTTP エンドポイントや RabbitMQ、Kafka などのメッセージストリームのリスナー/パブリッシャーとして外部に公開することができます。

  • プログラミングスタイルの選択 - リアクティブ、命令形またはハイブリッド

  • 関数の構成と適応(たとえば、リアクティブ型の命令型関数の構成)

  • 複数の入力と出力を備えたリアクティブ関数のサポートにより、マージ、結合、その他の複雑なストリーミング操作を関数で処理できます

  • 入力と出力の透過的な型変換

  • ターゲットプラットフォームに固有のデプロイのパッケージ化関数(Project Riff、AWS Lambda など)

  • HTTP エンドポイントとして外部に関数を公開するためのアダプターなど

  • そのようなアプリケーションコンテキストを含む JAR ファイルを分離されたクラスローダーでデプロイし、単一の JVM にまとめてパックできるようにします

  • Java 関数の本体である文字列をバイトコードにコンパイルしてから、上記のようにラップできる @Beans に変換します

  • AWS Lambda [GitHub] (英語) Azure [GitHub] (英語) Google クラウド機能 [GitHub] (英語) Apache OpenWhisk [GitHub] (英語) 、場合によっては他の「サーバーレス」サービスプロバイダー用のアダプター

Spring Cloud は、制限のない Apache 2.0 ライセンスでリリースされています。ドキュメントのこのセクションに貢献したい場合、またはエラーを見つけた場合は、github (英語) のプロジェクトでソースコードと課題追跡ツールを見つけてください。

入門

コマンドラインからビルド(およびサンプルを「インストール」):

$ ./mvnw clean install

(YOLO が必要な場合は、-DskipTests を追加してください)

サンプルの 1 つを実行します。

$ java -jar spring-cloud-function-samples/function-sample/target/*.jar

これにより、アプリが実行され、HTTP を介してその関数が公開されるため、次のように文字列を大文字に変換できます。

$ curl -H "Content-Type: text/plain" localhost:8080/uppercase -d Hello
HELLO

複数の文字列(Flux<String>)を新しい行で区切ることで変換できます

$ curl -H "Content-Type: text/plain" localhost:8080/uppercase -d 'Hello
> World'
HELLOWORLD

(ターミナルで QJ を使用して、そのようなリテラル文字列に改行を挿入できます)

プログラミングモデル

関数カタログと柔軟な関数シグネチャー

Spring Cloud Function の主な機能の 1 つは、一貫した実行モデルを提供しながら、ユーザー定義関数のさまざまな型シグネチャーを適応させてサポートすることです。そのため、すべてのユーザー定義関数は FunctionCatalog によって標準表現に変換されます。

通常、ユーザーは FunctionCatalog をまったく気にする必要はありませんが、ユーザーコードでサポートされている機能の種類を知っておくと便利です。

Spring Cloud Function はプロジェクト Reactor (英語) によって提供されるリアクティブ API のファーストクラスのサポートを提供し、Mono や Flux などのリアクティブプリミティブをユーザー定義関数の型として使用できるようにし、関数実装のプログラミングモデルを選択する際の柔軟性を高めることを理解することも重要です。リアクティブプログラミングモデルは、命令型プログラミングスタイルを使用して実装することが困難または不可能な機能の関数サポートも可能にします。詳細については、関数アリティセクションを参照してください。

Java 8 関数のサポート

Spring Cloud Function は、Java によって定義され、Java 8 以降で使用できる 3 つのコア関数インターフェースを採用して構築されています。

  • Supplier<O>

  • Function<I, O>

  • Consumer<I>

サプライヤー

サプライヤーは、リアクティブSupplier<Flux<T>>)または命令型Supplier<T>)にすることができます。呼び出しの観点からは、このようなサプライヤーの実装者にとって違いはありません。ただし、フレームワーク内(Spring Cloud Stream など)で使用される場合、サプライヤー、特にリアクティブは、ストリームのソースを表すために使用されることが多いため、コンシューマーがサブスクライブできるストリーム(Flux など)を取得するために一度呼び出されます。言い換えると、このようなサプライヤーは無限ストリームと同等です。ただし、同じリアクティブサプライヤーは、有限ストリーム(ポーリングされた JDBC データの結果セットなど)を表すこともできます。そのような場合、このようなリアクティブサプライヤーは、基盤となるフレームワークの何らかのポーリングメカニズムに接続する必要があります。

その支援のために、Spring Cloud Function はマーカーアノテーション org.springframework.cloud.function.context.PollableSupplier を提供して、そのようなサプライヤーが有限のストリームを生成し、再度ポーリングする必要がある可能性があることを通知します。とはいえ、Spring Cloud Function 自体はこのアノテーションの動作を提供しないことを理解することが重要です。

さらに、PollableSupplier アノテーションは、生成されたストリームを分割する必要があることを示すために、分割可能な属性を公開します。 ( スプリッター EIP (英語) を参照してください)

次に例を示します。

@PollableSupplier(splittable = true)
public Supplier<Flux<String>> someSupplier() {
	return () -> {
		String v1 = String.valueOf(System.nanoTime());
		String v2 = String.valueOf(System.nanoTime());
		String v3 = String.valueOf(System.nanoTime());
		return Flux.just(v1, v2, v3);
	};
}

関数

関数は命令型またはリアクティブ型の方法で記述することもできますが、サプライヤーやコンシューマーとは異なり、Spring Cloud Stream などのフレームワーク内で使用される場合、リアクティブ関数はストリーム (Flux または Mono) への参照を渡すために 1 回だけ呼び出され、命令型はイベントごとに 1 回呼び出されること以外に、実装者にとって特別な考慮事項はありません。

コンシューマー

Consumer は、少なくとも潜在的にはブロッキングを意味する void 戻り型を持っているため、少し特殊です。ほとんどの場合、Consumer<Flux<?>> を記述する必要はありませんが、そうする必要がある場合は、入力 flux をサブスクライブすることを忘れないでください。

関数合成

関数の合成は、複数の関数を 1 つに合成できる機能です。コアサポートは、Java 8 以降で利用可能な Function.andThen(..) (標準 Javadoc) サポートで利用可能な関数合成機能に基づいています。ただし、それに加えて、いくつかの追加機能を提供します。

宣言型関数の合成

この機能を使用すると、spring.cloud.function.definition プロパティを提供するときに、| (パイプ)または , (コンマ)区切り文字を使用して宣言的な方法で構成命令を提供できます。

次に例を示します

--spring.cloud.function.definition=uppercase|reverse

ここでは、それ自体が関数 uppercase と関数 reverse の合成である単一の関数の定義を効果的に提供しました。関数の定義は、いくつかの名前の関数の合成することができるため実際には、プロパティ名を定義していない名前である理由の一つです。また、前述のように、パイプの代わりに , を使用できます(…​definition=uppercase,reverse など)。

非関数の作成

Spring Cloud Function は、Consumer または Function を使用したサプライヤーの作成、および Consumer を使用した Function の作成もサポートします。ここで重要なのは、そのような定義の最終製品を理解することです。機能を使用してサプライヤーを構成すると、サプライヤーになりますが、コンシューマーを使用してサプライヤーを構成すると、実質的に実行可能になります。コンシューマーで関数を構成する同じロジックに従うと、コンシューマーになります。

そしてもちろん、コンシューマーと機能、コンシューマーとサプライヤーなどの構成不可能なものを作成することはできません。

関数のルーティングとフィルタリング

2.2 Spring Cloud Function にはルーティング機能が搭載されており、1 つの関数を呼び出すと、その関数が実際に呼び出したい関数へのルーターとして機能します。この機能は、複数の機能の設定を維持することが面倒であったり、複数の機能を公開することができない特定の FAAS 環境において非常に有用です。

RoutingFunction は、functionRouter という名前で FunctionCatalog に登録されています。単純さと一貫性のために、RoutingFunction.FUNCTION_NAME 定数を参照することもできます。

この関数には次のシグネチャーがあります。

public class RoutingFunction implements Function<Object, Object> {
. . .
}

ルーティング命令は、いくつかの方法で伝達できます。

メッセージヘッダー

入力引数の型が Message<?> の場合、spring.cloud.function.definition または spring.cloud.function.routing-expression メッセージヘッダーのいずれかを設定することにより、ルーティング命令を通信できます。より静的なケースでは、spring.cloud.function.definition ヘッダーを使用して、単一の関数(…​definition=foo など)または合成命令(…​definition=foo|bar|baz など)の名前を指定できます。より動的なケースでは、Spring 式言語(SpEL)を使用して、関数の定義に解決される SpEL 式を提供できる spring.cloud.function.routing-expression ヘッダーを使用できます(上記のとおり)。

SpEL 評価コンテキストのルートオブジェクトは実際の入力引数であるため、Message<?> の場合、payload と headers の両方にアクセスできる式(spring.cloud.function.routing-expression=headers.function_name など)を作成できます。

特定の実行環境 / モデルでは、アダプターはメッセージヘッダーを介して spring.cloud.function.definition および / または spring.cloud.function.routing-expression を変換および通信する責任があります。例: spring-cloud-function-web を使用する場合、HTTP ヘッダーとして spring.cloud.function.definition を提供でき、フレームワークはそれと他の HTTP ヘッダーをメッセージヘッダーとして伝播します。

アプリケーションのプロパティ

ルーティング命令は、アプリケーションプロパティとして spring.cloud.function.definition または spring.cloud.function.routing-expression を介して通信することもできます。前のセクションで説明したルールは、ここでも適用されます。唯一の違いは、これらの命令をアプリケーションプロパティ(--spring.cloud.function.definition=foo など)として提供することです。

関数フィルタリングフィルタリングは、2 つのパス( "go" または "discard" )しかないルーティングの型です。関数に関しては、ある条件が "true" を返した場合にのみ特定の関数を呼び出したい、それ以外の場合は入力を破棄したいという意味です。ただし、入力を破棄することになると、アプリケーションのコンテキストでそれが何を意味するかについて多くの解釈があります。例: ログに記録したい場合や、破棄されたメッセージのカウンターを維持したい場合があります。また、何もしたくない場合もあります。これらの異なるパスのため、破棄されたメッセージを処理する方法の一般的な構成オプションは提供していません。代わりに、「破棄」パスを意味する単純なコンシューマーを定義することをお勧めします。

@Bean
public Consumer<?> devNull() {
   // log, count or whatever
}

これで、実際には 2 つのパスのみが効果的にフィルターになるルーティング式を作成できます。例:

--spring.cloud.function.routing-expression=headers.contentType.toString().equals('text/plain') ? 'echo' : 'devNull'

'echo' 関数に送られる条件に合わないすべてのメッセージは 'devNull' に送られ、そこでは何もできません。署名 Consumer<?> により、型変換が試行されないため、実行オーバーヘッドがほとんど発生しません。

リアクティブ入力(パブリッシャーなど)を処理する場合、ルーティング命令は関数プロパティを介してのみ提供する必要があります。これは、パブリッシャーを渡すために 1 回だけ呼び出され、残りはリアクターによって処理されるリアクティブ関数の性質によるものです。個々の値(メッセージなど)を介して通信されるルーティング命令にアクセスしたり、依存したりすることはできません。

関数アリティ

データのストリームを分類して整理する必要がある場合があります。例: たとえば、「オーダー」と「請求書」を含む組織化されていないデータを処理する典型的なビッグデータのユースケースを考えてみましょう。それぞれを別々のデータストアに入れたいとします。ここで、関数アリティ(複数の入力と出力を持つ関数)のサポートが役立ちます。

そのような関数の例を見てみましょう(完全な実装の詳細はここにあります)

@Bean
public Function<Flux<Integer>, Tuple2<Flux<String>, Flux<String>>> organise() {
	return flux -> ...;
}

プロジェクト Reactor が SCF のコア依存関係であることを考えると、そのタプルライブラリを使用しています。タプルは、カーディナリティ情報の両方を伝達することにより、独自の利点を提供します。どちらも SCSt の文脈では非常に重要です。カーディナリティにより、作成して関数の対応する入力と出力にバインドする必要のある入力バインディングと出力バインディングの数を知ることができます。型情報を認識することで、適切な型変換が保証されます。

また、この関数では、2 つの出力バインディング名が organise-out-0 と organise-out-1 であるため、ここでバインディング名の命名規則の「インデックス」部分が機能します。

IMPORTANT: 現時点では、関数のアリティは、複合イベント処理を中心としたリアクティブ関数(Function<TupleN<Flux<?>…​>, TupleN<Flux<?>…​>>)でのみサポートされています。複合イベント処理では、イベントの合流点の評価と計算では、通常、単一のイベントではなく、イベントのストリームを表示する必要があります。

型変換 (コンテンツ型のネゴシエーション)

コンテンツ型ネゴシエーションは Spring Cloud Function のコア機能の 1 つです。これにより、受信データを関数シグネチャーで宣言された型に変換できるだけでなく、関数の合成中に同じ変換を実行して、他の方法では(型ごとに)合成できない関数にすることができます。構成可能。

コンテンツ型ネゴシエーションの背後にあるメカニズムと必要性をよりよく理解するために、例として次の関数を使用して、非常に単純なユースケースを見ていきます。

@Bean
public Function<Person, String> personFunction {..}

前の例に示されている関数は、引数として Person オブジェクトを想定し、出力として文字列型を生成します。このような関数が型 Person で呼び出された場合、すべてが正常に機能します。ただし、通常、関数は、byte[]JSON String などの生の形式で提供されることが最も多い受信データのハンドラーのロールを果たします。フレームワークが受信データを引数としてこの関数に渡すことに成功するには、次のことを行う必要があります。どういうわけか、受信データを Person 型に変換します。

Spring Cloud Function は、それを実現するために Spring メカニズムに固有の 2 つのメカニズムに依存しています。

  1. MessageConverter- 受信メッセージデータから関数によって宣言された型に変換します。

  2. ConversionService- 受信非メッセージデータから関数によって宣言された型に変換します。

これは、生データの型(メッセージまたは非メッセージ)に応じて、Spring Cloud Function がいずれかのメカニズムを適用することを意味します。

他のリクエスト(HTTP、メッセージングなど)の一部として呼び出される関数を処理する場合、ほとんどの場合、フレームワークは MessageConverters に依存します。これは、そのようなリクエストがすでに Spring Message に変換されているためです。つまり、フレームワークは適切な MessageConverter を見つけて適用します。これを実現するには、フレームワークにユーザーからの指示が必要です。これらの命令の 1 つは、関数自体の署名(Person 型)によってすでに提供されています。理論的には、それで十分なはずです(場合によってはそれで十分です)。ただし、ほとんどのユースケースでは、適切な MessageConverter を選択するために、フレームワークに追加の情報が必要です。その欠落している部分は contentType ヘッダーです。

このようなヘッダーは通常、メッセージの一部として提供され、最初にそのようなメッセージを作成した対応するアダプターによって挿入されます。例: HTTP POST リクエストでは、コンテンツ型の HTTP ヘッダーがメッセージの contentType ヘッダーにコピーされます。

そのようなヘッダーが存在しない場合、フレームワークは application/json などのデフォルトのコンテンツ型に依存します。

コンテンツ型と引数型

前述のように、フレームワークが適切な MessageConverter を選択するには、引数型と、オプションでコンテンツ型情報が必要です。適切な MessageConverter を選択するためのロジックは、ユーザー定義関数の呼び出しの直前(実際の引数型がフレームワークに認識されている場合)にトリガーされる引数リゾルバーにあります。引数の型が現在のペイロードの型と一致しない場合、フレームワークは事前構成された MessageConverters のスタックに委譲して、それらのいずれかがペイロードを変換できるかどうかを確認します。

contentType と引数型の組み合わせは、フレームワークが適切な MessageConverter を見つけることによってメッセージをターゲット型に変換できるかどうかを判断するメカニズムです。適切な MessageConverter が見つからない場合は、例外がスローされます。これは、カスタム MessageConverter を追加することで処理できます(User-defined Message Converters を参照)。

Message が contentType のみに基づいて他の型に変換されることを期待しないでください。contentType はターゲット型を補完することを忘れないでください。これは、MessageConverter が考慮に入れる場合と考慮しない場合があるヒントです。

メッセージコンバーター

MessageConverters は、次の 2 つのメソッドを定義します。

Object fromMessage(Message<?> message, Class<?> targetClass);

Message<?> toMessage(Object payload, @Nullable MessageHeaders headers);

特に Spring Cloud Stream のコンテキストでは、これらのメソッドの契約とその使用箇所を理解することが重要です。

fromMessage メソッドは、受信 Message を引数型に変換します。Message のペイロードは任意の型にすることができ、複数の型をサポートするのは MessageConverter の実際の実装次第です。

提供された MessageConverters

前述のように、フレームワークは、最も一般的なユースケースを処理するための MessageConverters のスタックをすでに提供しています。次のリストは、提供された MessageConverters を優先順に説明しています(最初に機能する MessageConverter が使用されます)。

  1. JsonMessageConverter: Jackson または Gson ライブラリ(DEFAULT)を使用して contentType が application/json である場合に、Message のペイロードを POJO との間で変換することをサポートします。

  2. ByteArrayMessageConvertercontentType が application/octet-stream の場合に、Message のペイロードを byte[] から byte[] に変換することをサポートします。これは本質的にパススルーであり、主に下位互換性のために存在します。

  3. StringMessageConvertercontentType が text/plain の場合、任意の型の String への変換をサポートします。

適切なコンバーターが見つからない場合、フレームワークは例外をスローします。その場合は、コードと構成をチェックして、何も見逃していないことを確認する必要があります(つまり、バインディングまたはヘッダーを使用して contentType を提供したことを確認してください)。ただし、ほとんどの場合、いくつかのまれなケース(おそらく、カスタム contentType など)が見つかり、提供されている MessageConverters の現在のスタックは変換方法を認識していません。その場合は、カスタム MessageConverter を追加できます。ユーザー定義のメッセージコンバーターを参照してください。

ユーザー定義のメッセージコンバーター

Spring Cloud Function は、追加の MessageConverters を定義および登録するメカニズムを公開しています。これを使用するには、org.springframework.messaging.converter.MessageConverter を実装し、@Bean として構成します。次に、`MessageConverter` の既存のスタックに追加されます。

カスタム MessageConverter 実装が既存のスタックの先頭に追加されることを理解することが重要です。その結果、カスタム MessageConverter 実装は既存の実装よりも優先され、既存のコンバーターをオーバーライドしたり、追加したりすることができます。

次の例は、application/bar と呼ばれる新しいコンテンツ型をサポートするメッセージコンバーター Bean を作成する方法を示しています。

@SpringBootApplication
public static class SinkApplication {

    ...

    @Bean
    public MessageConverter customMessageConverter() {
        return new MyCustomMessageConverter();
    }
}

public class MyCustomMessageConverter extends AbstractMessageConverter {

    public MyCustomMessageConverter() {
        super(new MimeType("application", "bar"));
    }

    @Override
    protected boolean supports(Class<?> clazz) {
        return (Bar.class.equals(clazz));
    }

    @Override
    protected Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) {
        Object payload = message.getPayload();
        return (payload instanceof Bar ? payload : new Bar((byte[]) payload));
    }
}

Kotlin Lambda サポート

また、Kotlin ラムダのサポートも提供しています(v2.0 以降)。次のことを考慮してください。

@Bean
open fun kotlinSupplier(): () -> String {
    return  { "Hello from Kotlin" }
}

@Bean
open fun kotlinFunction(): (String) -> String {
    return  { it.toUpperCase() }
}

@Bean
open fun kotlinConsumer(): (String) -> Unit {
    return  { println(it) }
}

上記は、Spring Bean として構成された Kotlin ラムダを表しています。各署名は、Java で SupplierFunctionConsumer に相当するものにマップされるため、フレームワークによってサポート / 認識される署名になります。Kotlin から Java へのマッピングの仕組みはこのドキュメントの範囲外ですが、「Java 8 関数のサポート」セクションで概説されている署名変換の同じルールがここでも適用されることを理解することが重要です。

Kotlin サポートを有効にするには、適切な自動構成とサポートクラスを含む spring-cloud-function-kotlin モジュールをクラスパスに追加するだけです。

関数コンポーネントスキャン

Spring Cloud Function は、functions と呼ばれるパッケージ内の FunctionConsumerSupplier の実装が存在する場合、それをスキャンします。この機能を使用すると、Spring に依存しない関数を記述できます。@Component アノテーションも必要ありません。別のパッケージを使用する場合は、spring.cloud.function.scan.packages を設定できます。spring.cloud.function.scan.enabled=false を使用して、スキャンを完全にオフにすることもできます。

スタンドアロン Web アプリケーション

関数は、HTTP エンドポイントとして自動的にエクスポートできます。

spring-cloud-function-web モジュールには、Spring Boot Web アプリケーション(MVC サポート付き)に含まれているときにアクティブになる自動構成があります。簡単な開始体験が必要な場合に備えて、すべてのオプションの依存関係を収集する spring-cloud-starter-function-web もあります。

Web 構成をアクティブにすると、アプリには MVC エンドポイント(デフォルトでは "/" にありますが、spring.cloud.function.web.path で構成可能)があり、関数名が URL パスの一部になるアプリケーションコンテキストで関数にアクセスするために使用できます。サポートされているコンテンツ型はプレーンテキストと JSON です。

メソッド パス リクエスト レスポンス ステータス

GET

/{supplier}

-

指定されたサプライヤーからのアイテム

200 OK

POST

/{consumer}

JSON オブジェクトまたはテキスト

入力をミラーリングし、リクエスト本文をコンシューマーにプッシュします

202 承認済み

POST

/{consumer}

JSON 配列または新しい行のテキスト

入力をミラーリングし、本文を 1 つずつコンシューマーにプッシュします

202 承認済み

POST

/{function}

JSON オブジェクトまたはテキスト

名前付き関数を適用した結果

200 OK

POST

/{function}

JSON 配列または新しい行のテキスト

名前付き関数を適用した結果

200 OK

GET

/{function}/{item}

-

アイテムをオブジェクトに変換し、関数を適用した結果を返します

200 OK

上の表が示すように、エンドポイントの動作は、メソッドと受信リクエストデータの型によって異なります。受信データが単一値であり、ターゲット関数が明らかに単一値として宣言されている場合(つまり、コレクションまたは Flux を返さない場合)、レスポンスにも単一値が含まれます。複数値のレスポンスの場合、クライアントは "Accept:text/event-stream" を送信することにより、サーバーから送信されたイベントストリームをリクエストできます。

Message<?> で入出力を使用して宣言された関数とコンシューマーには、入力メッセージのリクエストヘッダーが表示され、出力メッセージヘッダーは HTTP ヘッダーに変換されます。

テキストを POST する場合、レスポンス形式は、コンテンツネゴシエーションに応じて、Spring Boot 2.0 以前のバージョンとは異なる場合があります(最良の結果を得るには、コンテンツ型を指定し、ヘッダーを受け入れます)。

このようなアプリケーションをテストする方法の詳細と例については、関数アプリケーションのテストを参照してください。

関数マッピングルール

カタログに関数(コンシューマーなど)が 1 つしかない場合、パス内の名前はオプションです。つまり、カタログ curl -H "Content-Type: text/plain" localhost:8080/uppercase -d hello に uppercase 関数しかない場合、curl -H "Content-Type: text/plain" localhost:8080/ -d hello 呼び出しは同じです。

複合関数は、パイプまたはコンマを使用して関数名を区切ることができます(パイプは、URL パスでは有効ですが、コマンドラインで入力するのは少し厄介です)。例: curl -H "Content-Type: text/plain" localhost:8080/uppercase,reverse -d hello

カタログに複数の関数がある場合、各関数はエクスポートされ、パスの一部である関数名(localhost:8080/uppercase など)でマップされます。このシナリオでも、spring.cloud.function.definition プロパティを提供することにより、特定の関数または関数構成をルートパスにマップできます。

以下に例を示します。

--spring.cloud.function.definition=foo|bar

上記のプロパティは、"foo" および "bar" 関数を構成し、構成された関数を "/" パスにマップします。

関数フィルタリングルール

カタログに複数の関数がある状況では、特定の関数または関数構成のみをエクスポートする必要がある場合があります。その場合、; で区切られてエクスポートするのと同じ spring.cloud.function.definition プロパティリスト関数を使用できます。この場合、ルートパスには何もマップされず、リストされていない関数(コンポジションを含む)はエクスポートされないことに注意してください。

以下に例を示します。

--spring.cloud.function.definition=foo;bar

これにより、カタログで使用可能な関数の数(localhost:8080/foo など)に関係なく、関数 foo と関数 bar のみがエクスポートされます。

--spring.cloud.function.definition=foo|bar;baz

これにより、カタログで使用可能な関数の数(localhost:8080/foo,bar など)に関係なく、関数構成 foo|bar と関数 baz のみがエクスポートされます。

スタンドアロンストリーミングアプリケーション

ブローカー(RabbitMQ や Kafka など)からメッセージを送受信するには、spring-cloud-stream プロジェクトと Spring Cloud Function との統合を活用できます。詳細と例については、Spring Cloud Stream リファレンスマニュアルの Spring Cloud Function (英語) セクションを参照してください。

パッケージ化された関数のデプロイ

Spring Cloud Function は、分離されたクラスローダーを使用して jar ファイル(またはデプロイされたアーカイブ、または jar ファイルのセット)を起動し、そこで定義された関数を公開できる「デプロイヤー」ライブラリを提供します。これは非常に強力なツールであり、たとえば、ターゲットの jar ファイルを変更せずに、さまざまな入出力アダプターに関数を適合させることができます。サーバーレスプラットフォームにはこの種の機能が組み込まれていることが多いため、このようなプラットフォームの関数呼び出し側の構成要素と見なすことができます(実際、リフ (英語) Java 関数呼び出し側はこのライブラリを使用します)。

標準のエントリポイントは、spring-cloud-function-deployer をクラスパスに追加することです。デプロイヤーが起動し、関数 jar の場所を指定するための構成を探します。

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-function-deployer</artifactId>
	<version>${spring.cloud.function.version}</version>
</dependency>

少なくとも、ユーザーは、関数を含むアーカイブの URL またはリソースの場所である spring.cloud.function.location を提供する必要があります。オプションで、maven: プレフィックスを使用して、依存関係ルックアップを介してアーティファクトを見つけることができます(詳細については、FunctionProperties を参照してください)。Spring Boot アプリケーションは jar ファイルからブートストラップされ、MANIFEST.MF を使用して開始クラスが検索されるため、たとえば、標準の Spring Boot fat jar が適切に機能します。ターゲット jar を正常に起動できる場合、結果はメインアプリケーションの FunctionCatalog に登録された関数になります。登録された関数は、分離されたクラスローダーで(deault によって)作成された場合でも、メインアプリケーションのコードで適用できます。

これは、「大文字」関数を含む JAR をデプロイし、それを呼び出す例です。

@SpringBootApplication
public class DeployFunctionDemo {

	public static void main(String[] args) {
		ApplicationContext context = SpringApplication.run(DeployFunctionDemo.class,
				"--spring.cloud.function.location=..../target/uppercase-0.0.1-SNAPSHOT.jar",
				"--spring.cloud.function.definition=uppercase");

		FunctionCatalog catalog = context.getBean(FunctionCatalog.class);
		Function<String, String> function = catalog.lookup("uppercase");
		System.out.println(function.apply("hello"));
	}
}

そして、これが Maven URI を使用した例です(FunctionDeployerTests のテストの 1 つから取得):

@SpringBootApplication
public class DeployFunctionDemo {

	public static void main(String[] args) {
		String[] args = new String[] {
				"--spring.cloud.function.location=maven://oz.demo:demo-uppercase:0.0.1-SNAPSHOT",
				"--spring.cloud.function.function-class=oz.demo.uppercase.MyFunction" };

		ApplicationContext context = SpringApplication.run(DeployerApplication.class, args);
		FunctionCatalog catalog = context.getBean(FunctionCatalog.class);
		Function<String, String> function = catalog.lookup("myFunction");

		assertThat(function.apply("bob")).isEqualTo("BOB");
	}
}

ローカルおよびリモートリポジトリ、ユーザー、パスワードなどの Maven リソースは、ローカルのデフォルトを効果的に使用し、ほとんどの場合に機能するデフォルトの MavenProperties を使用して解決されることに注意してください。ただし、カスタマイズする必要がある場合は、型 MavenProperties の Bean を提供するだけで、追加のプロパティを設定できます(以下の例を参照)。

@Bean
public MavenProperties mavenProperties() {
	MavenProperties properties = new MavenProperties();
	properties.setLocalRepository("target/it/");
	return properties;
}

サポートされているパッケージングシナリオ

現在、Spring Cloud Function はいくつかのパッケージングシナリオをサポートしており、機能のデプロイに関して最も柔軟性があります。

シンプルな JAR

このパッケージオプションは、Spring に関連するものに依存しないことを意味します。たとえば ; このような JAR には次のクラスが含まれていると考えてください。

package function.example;
. . .
public class UpperCaseFunction implements Function<String, String> {
	@Override
	public String apply(String value) {
		return value.toUpperCase();
	}
}

このようなパッケージをデプロイするときに行う必要があるのは、location および function-class プロパティを指定することだけです。

--spring.cloud.function.location=target/it/simplestjar/target/simplestjar-1.0.0.RELEASE.jar
--spring.cloud.function.function-class=function.example.UpperCaseFunction

場合によっては、複数の関数を一緒にパッケージ化することも考えられます。このようなシナリオでは、spring.cloud.function.function-class プロパティを使用して、; で区切るいくつかのクラスを一覧表示できます。

以下に例を示します。

--spring.cloud.function.function-class=function.example.UpperCaseFunction;function.example.ReverseFunction

ここでは、デプロイする 2 つの関数を特定しています。これらの関数には、関数カタログで名前でアクセスできます(例: catalog.lookup("reverseFunction");)。

詳細については、こちらから [GitHub] (英語) 入手できる完全なサンプルを参照してください。対応するテストは FunctionDeployerTests [GitHub] (英語) にもあります。

Spring Boot JAR

このパッケージ化オプションは、Spring Boot に依存しており、JAR が Spring Boot JAR として生成されたことを意味します。とはいえ、デプロイされた JAR が分離されたクラスローダーで実行されることを考えると、実際のデプロイヤーが使用する Spring Boot バージョンとバージョンの競合は発生しません。たとえば ; このような JAR には、次のクラスが含まれていることを考慮してください(Spring/Spring Boot がクラスパス上にある場合は、Spring の依存関係が追加される可能性があります)。

package function.example;
. . .
public class UpperCaseFunction implements Function<String, String> {
	@Override
	public String apply(String value) {
		return value.toUpperCase();
	}
}

以前と同様に、このようなパッケージをデプロイするときに、location プロパティと function-class プロパティを指定するだけです。

--spring.cloud.function.location=target/it/simplestjar/target/simplestjar-1.0.0.RELEASE.jar
--spring.cloud.function.function-class=function.example.UpperCaseFunction

詳細については、こちらから [GitHub] (英語) 入手できる完全なサンプルを参照してください。対応するテストは FunctionDeployerTests [GitHub] (英語) にもあります。

Spring Boot アプリケーション

このパッケージ化オプションは、JAR がマネージド Spring Bean として機能する完全なスタンドアロン Spring Boot アプリケーションであることを意味します。以前と同様に、Spring Boot に依存しており、JAR が Spring Boot JAR として生成されたという明らかな仮定があります。とはいえ、デプロイされた JAR が分離されたクラスローダーで実行されることを考えると、実際のデプロイヤーが使用する Spring Boot バージョンとバージョンの競合は発生しません。たとえば ; このような JAR には次のクラスが含まれていると考えてください。

package function.example;
. . .
@SpringBootApplication
public class SimpleFunctionAppApplication {

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

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

別の Spring アプリケーションコンテキストを効果的に処理しており、関数が Spring マネージド Bean であることを考えると、location プロパティに加えて、function-class の代わりに definition プロパティも指定します。

--spring.cloud.function.location=target/it/bootapp/target/bootapp-1.0.0.RELEASE-exec.jar
--spring.cloud.function.definition=uppercase

詳細については、こちらから [GitHub] (英語) 入手できる完全なサンプルを参照してください。対応するテストは FunctionDeployerTests [GitHub] (英語) にもあります。

この特定のデプロイオプションでは、クラスパスに Spring Cloud Function が含まれる場合と含まれない場合があります。デプロイヤーの観点からは、これは重要ではありません。

関数 Bean の定義

Spring Cloud Function は、高速起動が必要な小さなアプリ向けに、「関数」スタイルの Bean 宣言をサポートしています。Bean 宣言の機能スタイルは、5.1 が大幅に強化された Spring Framework 5.0 の機能でした。

関数と従来の Bean 定義の比較

これは、おなじみの @Configuration および @Bean 宣言スタイルを備えたバニラ Spring Cloud Function アプリケーションです。

@SpringBootApplication
public class DemoApplication {

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

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

}

関数 Bean の場合: ユーザーアプリケーションコードは、次のように「関数」形式に再キャストできます。

@SpringBootConfiguration
public class DemoApplication implements ApplicationContextInitializer<GenericApplicationContext> {

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

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

  @Override
  public void initialize(GenericApplicationContext context) {
    context.registerBean("demo", FunctionRegistration.class,
        () -> new FunctionRegistration<>(uppercase())
            .type(FunctionType.from(String.class).to(String.class)));
  }

}

主な違いは次のとおりです。

  • メインクラスは ApplicationContextInitializer です。

  • @Bean メソッドは context.registerBean() への呼び出しに変換されました

  • @SpringBootApplication は @SpringBootConfiguration に置き換えられ、Spring Boot 自動構成を有効にしていないことを示していますが、クラスは「エントリポイント」としてマークされています。

  • Spring Boot の SpringApplication は、Spring Cloud Function の FunctionalSpringApplication に置き換えられました(これはサブクラスです)。

Spring Cloud Function アプリに登録するビジネスロジック Bean の型は FunctionRegistration です。これは、関数と、入力型と出力型に関する情報の両方を含むラッパーです。アプリケーションの @Bean 形式では、その情報は反射的に取得できますが、関数 Bean 登録では、FunctionRegistration を使用しない限り、その一部が失われます。

ApplicationContextInitializer および FunctionRegistration を使用する代わりに、アプリケーション自体に Function (または Consumer または Supplier)を実装させることもできます。例(上記と同等):

@SpringBootConfiguration
public class DemoApplication implements Function<String, String> {

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

  @Override
  public String apply(String value) {
    return value.toUpperCase();
  }

}

型 Function の別個のスタンドアロンクラスを追加し、run() メソッドの代替形式を使用して SpringApplication に登録する場合にも機能します。主なことは、ジェネリクス型の情報は、実行時にクラス宣言を通じて利用できるということです。

持っているとしましょう

@Component
public class CustomFunction implements Function<Flux<Foo>, Flux<Bar>> {
	@Override
	public Flux<Bar> apply(Flux<Foo> flux) {
		return flux.map(foo -> new Bar("This is a Bar object from Foo value: " + foo.getValue()));
	}

}

それをそのように登録します:

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

関数 Bean 宣言の制限

ほとんどの Spring Cloud Function アプリは、Spring Boot 全体に比べてスコープが比較的小さいため、これらの関数 Bean 定義に簡単に適合させることができます。その限られた範囲を超えた場合は、@Bean スタイルの構成に戻すか、ハイブリッドアプローチを使用して、Spring Cloud Function アプリを継承できます。たとえば、外部データストアとの統合に Spring Boot 自動構成を利用する場合は、@EnableAutoConfiguration を使用する必要があります。必要に応じて関数宣言を使用して関数を定義することもできますが(つまり、「ハイブリッド」スタイル)、その場合は、Spring Boot が制御を取り戻すことができるように、spring.functional.enabled=false を使用して「完全関数モード」を明示的にオフにする必要があります。

関数アプリケーションのテスト

Spring Cloud Function には、Spring Boot ユーザーにとって非常に馴染みのある統合テスト用のユーティリティもいくつかあります。

これがアプリケーションであるとします。

@SpringBootApplication
public class SampleFunctionApplication {

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

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

このアプリケーションをラップする HTTP サーバーの統合テストは次のとおりです。

@SpringBootTest(classes = SampleFunctionApplication.class,
            webEnvironment = WebEnvironment.RANDOM_PORT)
public class WebFunctionTests {

    @Autowired
    private TestRestTemplate rest;

    @Test
    public void test() throws Exception {
        ResponseEntity<String> result = this.rest.exchange(
            RequestEntity.post(new URI("/uppercase")).body("hello"), String.class);
        System.out.println(result.getBody());
    }
}

または、関数 Bean 定義スタイルが使用されている場合:

@FunctionalSpringBootTest
public class WebFunctionTests {

    @Autowired
    private TestRestTemplate rest;

    @Test
    public void test() throws Exception {
        ResponseEntity<String> result = this.rest.exchange(
            RequestEntity.post(new URI("/uppercase")).body("hello"), String.class);
        System.out.println(result.getBody());
    }
}

このテストは、同じアプリの @Bean バージョンに対して作成するテストとほぼ同じです。唯一の違いは、通常の @SpringBootTest ではなく @FunctionalSpringBootTest アノテーションです。@Autowired TestRestTemplate のような他のすべての部品は、標準の Spring Boot 機能です。

そして、ここで正しい依存関係を支援するために、POM からの抜粋があります

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    . . . .
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-function-web</artifactId>
        <version>3.0.1.BUILD-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

または、FunctionCatalog のみを使用して非 HTTP アプリのテストを作成することもできます。例:

@RunWith(SpringRunner.class)
@FunctionalSpringBootTest
public class FunctionalTests {

	@Autowired
	private FunctionCatalog catalog;

	@Test
	public void words() throws Exception {
		Function<String, String> function = catalog.lookup(Function.class,
				"uppercase");
		assertThat(function.apply("hello")).isEqualTo("HELLO");
	}

}

動的コンパイル

関数コンパイラーを使用して構成プロパティから関数を作成するサンプルアプリがあります。バニラの「関数サンプル」にもその機能があります。また、実行時にコンパイルが行われていることを確認するために実行できるスクリプトがいくつかあります。これらの例を実行するには、scripts ディレクトリに移動します。

cd scripts

また、RabbitMQ サーバーをローカルで起動します(たとえば、rabbitmq-server を実行します)。

関数レジストリサービスを開始します。

./function-registry.sh

関数を登録する:

./registerFunction.sh -n uppercase -f "f->f.map(s->s.toString().toUpperCase())"

その関数を使用して REST マイクロサービスを実行します。

./web.sh -f uppercase -p 9000
curl -H "Content-Type: text/plain" -H "Accept: text/plain" localhost:9000/uppercase -d foo

サプライヤーの登録:

./registerSupplier.sh -n words -f "()->Flux.just(\"foo\",\"bar\")"

そのサプライヤーを使用して REST マイクロサービスを実行します。

./web.sh -s words -p 9001
curl -H "Accept: application/json" localhost:9001/words

コンシューマーの登録:

./registerConsumer.sh -n print -t String -f "System.out::println"

そのコンシューマーを使用して REST マイクロサービスを実行します。

./web.sh -c print -p 9002
curl -X POST -H "Content-Type: text/plain" -d foo localhost:9002/print

ストリーム処理マイクロサービスの実行:

最初にストリーミングワードサプライヤーを登録します。

./registerSupplier.sh -n wordstream -f "()->Flux.interval(Duration.ofMillis(1000)).map(i->\"message-\"+i)"

次に、ソース(サプライヤー)、プロセッサー(関数)、シンク(コンシューマー)アプリを(逆の順序で)起動します。

./stream.sh -p 9103 -i uppercaseWords -c print
./stream.sh -p 9102 -i words -f uppercase -o uppercaseWords
./stream.sh -p 9101 -s wordstream -o words

出力はシンクアプリのコンソールに表示されます(1 秒に 1 つのメッセージ、大文字に変換されます)。

MESSAGE-0
MESSAGE-1
MESSAGE-2
MESSAGE-3
MESSAGE-4
MESSAGE-5
MESSAGE-6
MESSAGE-7
MESSAGE-8
MESSAGE-9
...

サーバーレスプラットフォームアダプター

Spring Cloud Function アプリケーションは、スタンドアロンプロセスとして実行できるだけでなく、既存のサーバーレスプラットフォームの 1 つを実行するように適合させることもできます。プロジェクトには、AWS Lambda [GitHub] (英語) Azure [GitHub] (英語) Apache OpenWhisk [GitHub] (英語) 用のアダプターがあります。Oracle Fn プラットフォーム [GitHub] (英語) には独自の Spring Cloud Function アダプターがあります。また、リフ (英語) は Java 関数をサポートし、その Java 関数呼び出し元 [GitHub] (英語) はネイティブに動作し、Spring Cloud Function jar 用のアダプターです。

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 を検索する検索順序に従って、デフォルトを見つけようとします)。

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 の自動構成に依存している場合は、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>
		</dependency>
	</dependencies>
	<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.schemas</resource>
			</transformer>
		</transformers>
	</configuration>
</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] (英語) を使用できます。セットアップの例は上記にあります。

theSpring 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'
	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, ?>。この場合、変換は試行せず、生の入力を関数に直接渡します。

Microsoft Azure

Azure (英語) アダプターは、Spring Cloud Function コンテキストをブートストラップし、必要に応じて Spring Boot 構成を使用して、Azure フレームワークからユーザー関数に関数呼び出しをチャネルします。Azure Functions は、プラットフォームに固有のユーザーコード内のアノテーションを含む、非常にユニークですが侵襲的なプログラミングモデルを備えています。Spring Cloud でこれを使用する最も簡単な方法は、基本クラスを継承し、その中に基本クラスのメソッドに委譲する @FunctionName アノテーションを含むメソッドを記述することです。

このプロジェクトは、Spring Cloud Function アプリケーション用のアダプターレイヤーを Azure に提供します。型 Function の単一の @Bean を使用してアプリを作成でき、JAR ファイルが正しくレイアウトされていれば、Azure でデプロイできます。

拡張する必要のある AzureSpringBootRequestHandler があり、入力型と出力型をアノテーション付きメソッドパラメーターとして提供します(Azure がクラスをインスペクションして JSON バインディングを作成できるようにします)。基本クラスには、実際の関数呼び出しを委譲できる 2 つの便利なメソッド(handleRequest と handleOutput)があるため、ほとんどの場合、関数には 1 行しかありません。

例:

public class FooHandler extends AzureSpringBootRequestHandler<Foo, Bar> {
	@FunctionName("uppercase")
	public Bar execute(@HttpTrigger(name = "req", methods = {HttpMethod.GET,
			HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<Foo>> request,
		ExecutionContext context) {
		return handleRequest(request.getBody().get(), context);
	}
}

この Azure ハンドラーは、Function<Foo,Bar> Bean(または Function<Publisher<Foo>,Publisher<Bar>>)に委譲します。一部の Azure トリガー(@CosmosDBTrigger など)は、List の入力型になります。その場合、Azure ハンドラーで ListString (生の JSON)にバインドできます。List 入力は、入力型 Map<String,Object>、または同じ型の Publisher または List の Function に委譲します。Function の出力は、List (1 対 1)または単一の値(集約)にすることができ、Azure 宣言の出力バインディングは一致する必要があります。

アプリに Function などの型の @Bean が複数ある場合は、function.name を構成して使用する @Bean を選択できます。または、Azure ハンドラーメソッドの @FunctionName を関数名と一致させると、そのように機能するはずです(複数の関数を持つ関数アプリの場合も同様です)。関数は Spring Cloud FunctionCatalog から抽出されるため、デフォルトの関数名は Bean 名と同じです。

Azure へのアクセス ExecutionContext

Azure ランタイムによって com.microsoft.azure.functions.ExecutionContext の形式で提供されるターゲット実行コンテキストにアクセスする必要がある場合があります。たとえば、そのようなニーズの 1 つはロギングであるため、Azure コンソールに表示できます。

そのために、Spring Cloud Function はアプリケーションコンテキストで ExecutionContext を Bean として登録するため、関数に挿入できます。たとえば

@Bean
public Function<Foo, Bar> uppercase(ExecutionContext targetContext) {
	return foo -> {
		targetContext.getLogger().info("Invoking 'uppercase' on " + foo.getValue());
		return new Bar(foo.getValue().toUpperCase());
	};
}

通常は型ベースのインジェクションで十分ですが、必要に応じて、登録されている Bean 名である targetExecutionContext を利用することもできます。

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

Azure の実行時には Spring Cloud Function Web は必要ないため、Azure にデプロイする JAR を作成する前にこれを除外できますが、含めても使用されないため、そのままにしても問題ありません。Azure 上の関数アプリケーションは、Maven プラグインによって生成されたアーカイブです。この関数は、このプロジェクトによって生成された JAR ファイル内に存在します。このサンプルでは、Azure がハンドラークラスを見つけられるように、シンレイアウトを使用して実行可能ファイル jar として作成します。必要に応じて、通常のフラット JAR ファイルを使用することもできます。依存関係は含めないでください。

ビルドファイルの設定

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

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

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

次に、プラグインを構成します。resourceGroupappName、その他のオプションのプロパティを指定して、アプリケーションに Azure 固有の構成を提供し、Azure に必要な function.json ファイルが生成されるように package ゴールの実行を追加する必要があります。完全なプラグインドキュメントは、プラグインリポジトリにあります [GitHub] (英語)

<plugin>
	<groupId>com.microsoft.azure</groupId>
	<artifactId>azure-functions-maven-plugin</artifactId>
	<configuration>
		<resourceGroup>${functionResourceGroup}</resourceGroup>
		<appName>${functionAppName}</appName>
	</configuration>
	<executions>
		<execution>
			<id>package-functions</id>
			<goals>
				<goal>package</goal>
			</goals>
		</execution>
	</executions>
</plugin>

また、プラグインによってスキャンされるファイルが Azure functions ステージングディレクトリにあることを確認する必要があります (ステージングディレクトリとそのデフォルトの場所の詳細については、プラグインリポジトリ [GitHub] (英語) を参照してください)。

Spring Cloud Function アプリケーションを Maven を使用して Microsoft Azure にデプロイするためのサンプル pom.xml ファイル全体は、ここにあります。

現在のところ、Maven プラグインのみが利用可能です。Gradle プラグインは、クラウドプラットフォームプロバイダーによって作成されていません。

ビルド

./mvnw -U clean package

サンプルの実行

他の Spring Cloud Function サンプルと同様に、サンプルをローカルで実行できます。



および curl -H "Content-Type: text/plain" localhost:8080/api/uppercase -d '{"value": "hello foobar"}'

az CLI アプリが必要になります(詳細については、https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-first-java-maven を参照してください)。Azure ランタイムに関数をデプロイするには:

$ az login
$ mvn azure-functions:deploy

別のターミナルでこれを試してください: curl https://<azure-function-url-from-the-log>/api/uppercase (英語) -d '{"value": "hello foobar!"}'。上記の機能に正しい URL を使用していることを確認してください。または、Azure ダッシュボード UI で関数をテストすることもできます(関数名をクリックし、右側に移動してテストをクリックし、右下の実行をクリックします)。

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

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

Google クラウド機能 (アルファ)

Google Cloud Functions アダプターを使用すると、Spring Cloud Function アプリを Google クラウド機能 サーバーレスプラットフォームで実行できるようになります。この関数は、オープンソース Google 関数 フレームワーク (Java 用) [GitHub] (英語) を使用してローカルで実行することも、GCP 上で実行することもできます。

プロジェクトの依存関係

プロジェクトに spring-cloud-function-adapter-gcp 依存関係を追加することから始めます。

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

	...
</dependencies>

さらに、デプロイする関数の JAR をビルドする spring-boot-maven-plugin を追加します。

spring-boot-maven-plugin の依存関係として spring-cloud-function-adapter-gcp も参照していることに注意してください。これは、Google Cloud Functions 上の デプロイ用の正しい JAR 形式で関数をパッケージ化するようにプラグインを変更するために必要です。
<plugin>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-maven-plugin</artifactId>
	<configuration>
		<outputDirectory>target/deploy</outputDirectory>
	</configuration>
	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-function-adapter-gcp</artifactId>
		</dependency>
	</dependencies>
</plugin>

最後に、Java 用 Google 関数 フレームワークの一部として提供される Maven プラグインを追加します。これにより、mvn function:run を介して関数をローカルでテストできるようになります。

関数ターゲットは常に org.springframework.cloud.function.adapter.gcp.GcfJarLauncher に設定する必要があります。これは、Google Cloud Functions プラットフォームから Spring Cloud Function へのエントリポイントとして機能するアダプタークラスです。
<plugin>
	<groupId>com.google.cloud.functions</groupId>
	<artifactId>function-maven-plugin</artifactId>
	<version>0.9.1</version>
	<configuration>
		<functionTarget>org.springframework.cloud.function.adapter.gcp.GcfJarLauncher</functionTarget>
		<port>8080</port>
	</configuration>
</plugin>

動作する pom.xml の完全な例は、Spring Cloud 関数 GCP サンプル [GitHub] (英語) にあります。

HTTP 関数

Google Cloud Functions は、HTTP リクエストによって呼び出される関数である HTTP 関数 のデプロイをサポートしています。以下のセクションでは、Spring Cloud Function を HTTP 関数としてデプロイする手順について説明します。

入門

簡単な Spring Cloud Function の例から始めましょう。

@SpringBootApplication
public class CloudFunctionMain {

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

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

resources/META-INF/MANIFEST.MF で構成のメインクラスを指定します。

Main-Class: com.example.CloudFunctionMain

次に、関数をローカルで実行します。これは、プロジェクトの依存関係セクションで説明されている Google Cloud Functions function-maven-plugin によって提供されます。

mvn function:run

HTTP 関数を呼び出します。

curl http://localhost:8080/ -d "hello"
GCP にデプロイする

2020 年 3 月現在、Google Cloud Functions for Java はアルファ版です。ホワイトリスト (英語) に登録して試すことができます。

アプリケーションをパッケージ化することから始めます。

mvn package

上記で定義したカスタム spring-boot-maven-plugin プラグインを追加した場合は、結果の JAR が target/deploy ディレクトリに表示されるはずです。この JAR は、デプロイから Google Cloud Functions 用に正しくフォーマットされています。

次に、Cloud SDK CLI がインストールされていることを確認します。

プロジェクトのベースディレクトリから、次のコマンドを実行してデプロイします。

gcloud alpha functions deploy function-sample-gcp-http \
--entry-point org.springframework.cloud.function.adapter.gcp.GcfJarLauncher \
--runtime java11 \
--trigger-http \
--source target/deploy \
--memory 512MB

HTTP 関数を呼び出します。

curl https://REGION-PROJECT_ID.cloudfunctions.net/function-sample-gcp-http -d "hello"

バックグラウンド関数

Google Cloud Functions は、Cloud Pub/Sub トピックのメッセージ、Cloud Storage バケットの変更、Firebase (英語) イベントなどのイベントに応じて間接的に呼び出されるバックグラウンド関数 のデプロイもサポートしています。

spring-cloud-function-adapter-gcp を使用すると、関数をバックグラウンド関数としてデプロイすることもできます。

以下のセクションでは、Cloud Pub/Sub トピックの背景関数を作成するプロセスについて説明します。ただし、ここでは説明されていない、バックグラウンド関数の実行をトリガーできるさまざまなイベント型がいくつかあります。これらはバックグラウンド関数がドキュメントをトリガーします で説明されています。

入門

GCF バックグラウンド関数として実行される単純な Spring Cloud Function から始めましょう。

@SpringBootApplication
public class BackgroundFunctionMain {

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

	@Bean
	public Consumer<PubSubMessage> pubSubFunction() {
		return message -> System.out.println("The Pub/Sub message data: " + message.getData());
	}
}

さらに、以下の定義でプロジェクトに PubSubMessage クラスを作成します。このクラスは、Pub/Sub トピックイベントで関数に渡される Pub/Sub イベント構造 を表します。

public class PubSubMessage {

	private String data;

	private Map<String, String> attributes;

	private String messageId;

	private String publishTime;

	public String getData() {
		return data;
	}

	public void setData(String data) {
		this.data = data;
	}

	public Map<String, String> getAttributes() {
		return attributes;
	}

	public void setAttributes(Map<String, String> attributes) {
		this.attributes = attributes;
	}

	public String getMessageId() {
		return messageId;
	}

	public void setMessageId(String messageId) {
		this.messageId = messageId;
	}

	public String getPublishTime() {
		return publishTime;
	}

	public void setPublishTime(String publishTime) {
		this.publishTime = publishTime;
	}

}

resources/META-INF/MANIFEST.MF で構成のメインクラスを指定します。

Main-Class: com.example.BackgroundFunctionMain

次に、関数をローカルで実行します。これは、プロジェクトの依存関係セクションで説明されている Google Cloud Functions function-maven-plugin によって提供されます。

mvn function:run

HTTP 関数を呼び出します。

curl localhost:8080 -H "Content-Type: application/json" -d '{"data":"hello"}'

ログを表示して、関数が呼び出されたことを確認します。

GCP にデプロイする

バックグラウンド関数を GCP にデプロイするには、最初にアプリケーションをパッケージ化します。

mvn package

上記で定義したカスタム spring-boot-maven-plugin プラグインを追加した場合は、結果の JAR が target/deploy ディレクトリに表示されるはずです。この JAR は、デプロイから Google Cloud Functions 用に正しくフォーマットされています。

次に、Cloud SDK CLI がインストールされていることを確認します。

プロジェクトのベースディレクトリから、次のコマンドを実行してデプロイします。

gcloud alpha functions deploy function-sample-gcp-background \
--entry-point org.springframework.cloud.function.adapter.gcp.GcfJarLauncher \
--runtime java11 \
--trigger-topic my-functions-topic \
--source target/deploy \
--memory 512MB

Google クラウド関数は、--trigger-topic で指定されたトピックにメッセージがパブリッシュされるたびに関数を呼び出すようになります。

バックグラウンド機能のテストと検証のウォークスルーについては、GCF バックグラウンド関数のサンプル [GitHub] (英語) を実行するための手順を参照してください。

サンプル関数

プロジェクトは、参照として次のサンプル関数を提供します。