Function Appを作成できるフレームワーク

このエントリは2023/06/12現在の情報に基づいています。将来の機能追加・変更に伴い、記載内容からの乖離が発生する可能性があります。

純粋に個人の興味である。純正のFunctionsフレームワークでローカルでのテスト、クラウド上での動作はもちろん可能なのだが、他の開発フレームワークで作るとどうなるのか、というのを2023/06/12現在の情報でまとめた。調査条件・動作環境は以下の通り。

  • Javaの開発フレームワーク
  • Durable Functionはこの調査の対象外
  • ローカル環境には、Function Core Toolsをインストール済み(Linux/Windows)
  • HTTP trigger & Output bindingで動作確認済み

本家、というかAzure Functionsのフレームワーク

たぶん最初に触るやつ。

コマンド ラインから Azure に Java 関数を作成する / Create a Java function in Azure from the command line
https://learn.microsoft.com/azure/azure-functions/create-first-function-cli-java

関数部分だけを記述すればよいので、注力すべきところが明確でわかりやすい。とはいえ、他の開発フレームワークに慣れている場合、お作法の違いに戸惑いが発生するのも事実。以下はArcheTypeを使って作成したもの。

public class Function {
    @FunctionName("HttpExample")
    public HttpResponseMessage run(
            @HttpTrigger(
                name = "req",
                methods = {HttpMethod.GET, HttpMethod.POST},
                authLevel = AuthorizationLevel.ANONYMOUS)
                HttpRequestMessage<Optional<String>> request,
            final ExecutionContext context) {
        context.getLogger().info("Java HTTP trigger processed a request.");
        // Parse query parameter
        final String query = request.getQueryParameters().get("name");
        final String name = request.getBody().orElse(query);
        if (name == null) {
            return request.createResponseBuilder(HttpStatus.BAD_REQUEST).body("Please pass a name on the query string or in the request body").build();
        } else {
            return request.createResponseBuilder(HttpStatus.OK).body("Hello, " + name).build();
        }
    }
}

Spring Cloud

SpringはVMwareが中心となってオープンソースとして開発されている開発フレームワーク。

Spring by VMware Tanzu
https://spring.io/

Springには様々なプロジェクトがあり、その中に、クラウドベンダー非依存な形でFunctionアプリケーションを作成できるようにするためのSpring Cloud Functionというプロジェクトがある。

Spring Cloud Function
https://spring.io/projects/spring-cloud-function

まずはSpringのブログエントリを読んでから、AzureのSpring Cloud Functionを使ったチュートリアルをを見ると、基礎部分を把握した上で、わかりやすい書き方を理解できるので、学習しやすいかもしれない(日本語訳が気になる場合には、英語版を見たほうがいい)。

Spring Cloud Function in Azure
https://learn.microsoft.com/azure/developer/java/spring-framework/getting-started-with-spring-cloud-function-in-azure
Spring Cloud Function for Azure Function
https://spring.io/blog/2023/02/24/spring-cloud-function-for-azure-function

他のフレームワークと違う点は、Spring Cloud Functionがjava.util.function.Function/SupplierもしくはConsumer インターフェースを使って、入出力型を含む関数の構造を定義しているところ。そのため、

  • functionインターフェースを作成
  • functionをBeanとして登録

が必要。

依存関係

Azure Functionsに対応するためには、spring-cloud-function-adapter-azureを追加する。2023/06/12現在の最新は4.0.3。

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

あとazure-functions-maven-pluginは、Azure Functionsデプロイのためのプラグインで、デプロイに必要なApp Service PlanやSKU、リソースグループなどを指定できる。2023/06/12現在の最新は1.26.0。

<plugin>
  <groupId>com.microsoft.azure</groupId>
  <artifactId>azure-functions-maven-plugin</artifactId>
  <version>1.26.0</version>
  <configuration>
      <appName>scf-samples</appName>
      <resourceGroup>java-functions-group</resourceGroup>
      <region>westus</region>
      <appServicePlanName>java-functions-app-service-plan</appServicePlanName>
      <pricingTier>EP1</pricingTier>
      <hostJson>${project.basedir}/src/main/resources/host.json</hostJson>
      <runtime>
          <os>linux</os>
          <javaVersion>17</javaVersion>
      </runtime>
      <funcPort>7071</funcPort>
      <appSettings>
          <property>
              <name>FUNCTIONS_EXTENSION_VERSION</name>
              <value>~4</value>
          </property>
      </appSettings>
  </configuration>
  <executions>
      <execution>
          <id>package-functions</id>
          <goals>
              <goal>package</goal>
          </goals>
      </execution>
  </executions>
</plugin>

Azure Functions
https://github.com/microsoft/azure-maven-plugins/wiki/Azure-Functions

実際のコードはこんな感じ。少々違いはあるが、おおよその構造は純正のFunctionフレームワークとあまり変わらない。

@Component
public class Function {
    @Autowired
    private GreetingService service;
    @FunctionName("greeting")
    public HttpResponseMessage execute(
            @HttpTrigger(
                    name="req",
                    methods = {HttpMethod.GET},
                    authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<User>> request,
            ExecutionContext context) {
        context.getLogger().warning("Using Java (" + System.getProperty("java.version") + ")");
        User user = request.getBody()
                .filter(u->u.getName()!=null)
                .orElseGet(()->new User(request.getQueryParameters().getOrDefault("name","NO NAME")));
        context.getLogger().warning("User: " + user.getName());
        return request.createResponseBuilder(HttpStatus.OK)
                .body(service.apply(user))
                .header("Content-Type", "application/json")
                .build();
    }
}

Quarkus

QuarkusはRed Hatが中心となってオープンソースとして開発されている開発フレームワーク。2023/06/12現在3.1.2が最新。

Quarkus – Supersonic Subatomic Java
https://quarkus.io/

Azureのドキュメントでは、FunqyベースでAzure Function Appを作成するクイックスタートがある。

Azure Functions 上で Quarkus を使用してサーバーレス Java アプリをデプロイする / Deploy serverless Java apps with Quarkus on Azure Functions
https://learn.microsoft.com/azure/azure-functions/functions-create-first-quarkus

だが、Quarkus 3でAzure Functionsとネイティブ統合されたため、Funqyを意識せずにFunction Appを作成できるようになった。以下の動画で、Java ChampionのDaniel Ohが説明している。

コードの雛型は、GradleやMavenでArcheTypeを使って作成することも、以下の方式で作成することもできる。

コードを見ると、以下の通り。@InjectされているGreetingServiceがあったりと少々違いはあるが、こちらも本家のコードとほとんど変わりがない。

public class Function {
    @Inject
    GreetingService service;
    @FunctionName("greeting")
    public HttpResponseMessage run(
            @HttpTrigger(
                name = "req",
                methods = {HttpMethod.GET},
                authLevel = AuthorizationLevel.ANONYMOUS)
                HttpRequestMessage<Optional<String>> request,
            final ExecutionContext context) {
        context.getLogger().info("Java HTTP trigger processed a request.");
        // Parse query parameter
        final String query = request.getQueryParameters().get("name");
        final String name = request.getBody().orElse(query);
        if (name == null) {
            return request.createResponseBuilder(HttpStatus.BAD_REQUEST).body("Please pass a name on the query string or in the request body").build();
        } else {
            return request.createResponseBuilder(HttpStatus.OK).body(service.greeting(name)).build();
        }
    }
}

Micronaut

Micronaut Foundationが中心になって、オープンソースとして開発しているフレームワークで、リフレクションなどの動的な部分をなくすことを目指している。2023/06/12現在3.9.3が最新 (現在4.x系を開発中)。

Micronaut
https://micronaut.io/

Azureのドキュメントには記載がないので、MicronautのAzure Functionに関するドキュメントを見るしかない。以下のドキュメントの通り、Micronautでは、

  • micronaut-azure-functionを使うSimple Azure Functions
  • micronaut-azure-function-httpを使うAzure HTTP Functions

の2種類の方法で対応している。前者はDependency Injectionを利用可能な、より低レベルの仕組み。後者は通常のMicronaut コントローラを記述し、Azure Functionで動作可能にする仕組み。

Azure Function Support
https://micronaut-projects.github.io/micronaut-azure/latest/guide/#azureFunction

Simple Azure Functions

以下はSimple Azure Functionsの例。Spring Cloudの記述を少々手直ししただけ、とも読める(例えば@Autowired@Injectに変更する、など)。

public class Function extends AzureFunction {
    @Inject
    GreetingService greetingService;
    @FunctionName("greeting")
    public HttpResponseMessage invoke(
            @HttpTrigger(
                name = "req",
                methods = {HttpMethod.GET, HttpMethod.POST},
                authLevel = AuthorizationLevel.ANONYMOUS)
                HttpRequestMessage<Optional<User>> request,
                final ExecutionContext context) {
        User user = request.getBody()
                .filter(u->u.getName()!=null)
                .orElseGet(()->new User(request.getQueryParameters().getOrDefault("name","NO NAME")));
        context.getLogger().warning("User: " + user.getName());
        return request.createResponseBuilder(HttpStatus.OK)
                .body(greetingService.apply(user))
                .header("Content-Type", "application/json")
                .build();
    }
}
Azure HTTP Functions

Azure HTTP Functionsの場合、Function定義の部分はSimple Azure Functionsと同じだが、コントローラを作成する点で、Micronaut使いにとってはわかりやすい(はず)。大きな違いは以下だが、サンプルコードを見ると一目瞭然。

  • @FunctionNameで指定するのは文字通り関数名であり、コンテキストパスには無関係(Simple Azure Functionsや純正のFunctionsフレームワークだと、/api/{FunctionName}とコンテキストパスに現れる)
  • Azure HTTP Functionsの場合、io.micronaut.azure.function.http.AzureHttpFunctionを継承する(Simple Azure Functionの場合はio.micronaut.azure.function.AzureFunctionを継承)
  • super.route(request, context)を返す
public class Function extends AzureHttpFunction {
    @FunctionName("greeting")
    public HttpResponseMessage hello(
            @HttpTrigger(
                    name = "req",
                    methods = {HttpMethod.GET,HttpMethod.POST},
                    route = "{*route}",
                    authLevel = AuthorizationLevel.ANONYMOUS)
            HttpRequestMessage<Optional<String>> request,
            ExecutionContext context) {
        if (context != null) {
            context.getLogger().info("Executing Function: " + getClass().getName());
        }
        return super.route(request, context);
    }
}

で、GET、POSTのハンドラは以下。このあたりはMicronautのお作法そのまま。

@Controller("/greeting")
public class GreetingController {
    final String format = "Hello, %s!";
    @Post( uri = "/",
            consumes = MediaType.APPLICATION_JSON,
            produces = MediaType.APPLICATION_JSON)
    public Greeting postHello(@Body Optional<User> user) {
        return new Greeting(String.format(format, user.orElse(new User("NO NAME")).getName()));
    }
    @Get(uri = "{?name}",
            produces = MediaType.APPLICATION_JSON)
    public Greeting getHello(Optional<String> name) {
        return new Greeting(String.format(format, name.orElse("NO NAME")));
    }
}

ここらあたりで

おそらく他のフレームワークでも対応しているものがあるはずだが、いったんここまでにしておく。

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください