関数 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(FunctionTypeUtils.functionType(String.class, 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 は、アクチュエーターエンドポイントおよびプログラムによる方法で FunctionCatalog で使用可能な機能の視覚化をサポートします。

プログラム的な方法

プログラムでアプリケーションコンテキスト内で使用可能な機能を確認するには、FunctionCatalog にアクセスするだけです。ここでは、カタログのサイズを取得するためのメソッド、ルックアップ関数、使用可能なすべての関数の名前を一覧表示できます。

以下に例を示します。

FunctionCatalog functionCatalog = context.getBean(FunctionCatalog.class);
int size = functionCatalog.size(); // will tell you how many functions available in catalog
Set<String> names = functionCatalog.getNames(null); will list the names of all the Function, Suppliers and Consumers available in catalog
. . .

アクチュエーター

アクチュエーターと Web はオプションであるため、最初に Web 依存関係の 1 つを追加し、アクチュエーター依存関係を手動で追加する必要があります。次の例は、Web フレームワークの依存関係を追加する方法を示しています。

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
</dependency>

次の例は、WebFlux フレームワークの依存関係を追加する方法を示しています。

<dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

次のように、アクチュエーターの依存関係を追加できます。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

また、次のプロパティを設定して、functions アクチュエーターのエンドポイントを有効にする必要があります: --management.endpoints.web.exposure.include=functions

次の URL にアクセスして、FunctionCatalog の機能を確認してください。<host>:<port>/actuator/functions (英語)

以下に例を示します。

curl http://localhost:8080/actuator/functions

出力は次のようになります。

{"charCounter":
	{"type":"FUNCTION","input-type":"string","output-type":"integer"},
 "logger":
 	{"type":"CONSUMER","input-type":"string"},
 "functionRouter":
 	{"type":"FUNCTION","input-type":"object","output-type":"object"},
 "words":
 	{"type":"SUPPLIER","output-type":"string"}. . .

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

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 アノテーションです。@AutowiredTestRestTemplate のような他のすべての部品は、標準の Spring Boot 機能です。

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

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    . . . .
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-function-web</artifactId>
        <version>4.1.2</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>
    </dependency>

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

@FunctionalSpringBootTest
public class FunctionalTests {

	@Autowired
	private FunctionCatalog catalog;

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

}