Java で Azure Functionsの関数を実装する

# Java で Azure Functionsの関数を実装する

Azure Functions とは、イベントドリブンでプログラムを実行してくれるSaaSプラットフォームです。いわゆるサーバレスプラットフォームと呼ばれるもので、AWS で言うのところの Lambda です（知らないんですけど）

Azure Functionsは、様々な言語とトリガサポートします。

言語では、

* C#
* GO
* Java
* JavaScript / TypeScript
* PowerShell
* Python
* Rust

トリガ（イベント）では、

* HTTP
* タイマー
* Storage Queue / ServiceBug
* CosmosDb
* ...

などです。

詳細は以下のDocsを当たってください。

[Azure Functions のドキュメント | Microsoft Docs](https://docs.microsoft.com/ja-jp/azure/azure-functions/?WT.mc_id=AZ-MVP-5001265)


# Javaからの利用

Azure Functions 用の Maven plugin がリリースされていますので、それを使いましょう。

[Maven Plugin for Azure Functions | Microsoft Docs](https://docs.microsoft.com/ja-jp/java/api/overview/azure/maven/azure-functions-maven-plugin/readme?view=azure-java-stable&WT.mc_id=AZ-MVP-5001265)

Functions プロジェクトの生成は、以下の通り `mvn` から `generate` します。サポートされている、Javaのバージョンは Java8 or Java11 です。`javaVersion`に使いたいJavaのバージョンを指定してください。

```sh
mvn archetype:generate -DarchetypeGroupId=com.microsoft.azure -DarchetypeArtifactId=azure-functions-archetype -DjavaVersion=8mvn archetype:generate -DarchetypeGroupId=com.microsoft.azure -DarchetypeArtifactId=azure-functions-archetype -DjavaVersion=8
```

デフォルトでHTTPトリガーが自動で生成されます。Web APIを実装できます。

```java
    @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();
        }
    }
```

# ローカル実行

ローカルで実行するには、Azure Functions Core Toolsが必要です。Visual Studio Code でプロジェクトを開くと、これらのツールのインストールが促されるのでインストールしてください。手動でもインストールできます。詳細は以下のURLへ。

[Azure Functions Core Tools の操作 | Microsoft Docs](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-run-local?tabs=windows%2Ccsharp%2Cbash&WT.mc_id=AZ-MVP-5001265)

インストールが終わると、`func` というコマンドがパスに通ると思いますが、Javaからはこのコマンドは直接利用せず、`mvn`経由で実行します。`package` してから `azure-functions:run`するようにしてください。

```
mvn package azure-functions:run
```

実行されると、以下のようにホストされた関数が表示されます。CURLなど `curl  http://localhost:7071/api/HttpExample?name=azure` でクセスするとレスポンスを取得出来ると思います。

```log
[INFO] Azure Functions Core Tools found.
Azure Functions Core Tools (3.0.2931 Commit hash: d552c6741a37422684f0efab41d541ebad2b2bd2)
Function Runtime Version: 3.0.14492.0
[2021-04-14T06:53:42.928] Worker process started and initialized.

Functions:

        HttpExample: [GET,POST] http://localhost:7071/api/HttpExample
``` 

# 関数の追加

関数の追加も`mvn` で行います。Java以外の言語は、`func` コマンドで実行したりもするのですが。

```
mvn azure-functions:add
```

対話的に聞かれるので、追加したいトリガーを選択します。

```
Choose from below options as template for new function
0. HttpTrigger
1. BlobTrigger
2. QueueTrigger
3. TimerTrigger
4. EventGridTrigger
5. EventHubTrigger
6. CosmosDBTrigger
7. ServiceBusQueueTrigger
8. ServiceBusTopicTrigger
Enter index to use: 3
[INFO] Selected function template: TimerTrigger
[INFO] Successfully found function template: TimerTrigger
[INFO]
[INFO] Step 3 of 4: Prepare required parameters
[INFO] Common parameter [Function Name]: name for both the new function and Java class
Enter value for Function Name: TimerTriggerFunction
[INFO] Common parameter [Package Name]: package name of the new Java class
Enter value for Package Name: com.example.moris
[INFO] Trigger specific parameter [schedule]:Enter a cron expression of the format '{second} {minute} {hour} {day} {month} {day of week}' to specify the schedule.
Enter value for schedule(Default: 0 * * * * *):
[INFO]
[INFO] Summary of parameters for function template:
[INFO] schedule: 0 * * * * *
[INFO] functionName: TimerTriggerFunction
[INFO] className: TimerTriggerFunction
[INFO] packageName: com.example.moris
[INFO]
[INFO] Step 4 of 4: Saving function to file
[INFO] Successfully saved new function at c:\p\moris\blog\functions-sample\src\main\java\com\example\moris\TimerTriggerFunction.java
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
```

# デプロイ

WebApps用のプラグインでは `config` があったのですが、 Functions 用のプラグインにはありません。あらかじめ構成したい場合は、先ほど紹介したDocsをみて`configuration`を追加してください。構成がないまま`azure-functions:deploy`すると、適当にAzureリソースが作られます。簡単にテストしたいときは、これで問題ないでしょう。以下がデプロイしたときに自動で生成されたものです。


```xml
                <configuration>
                    <!-- function app name -->
                    <appName>${functionAppName}</appName>
                    <!-- function app resource group -->
                    <resourceGroup>java-functions-group</resourceGroup>
                    <!-- function app service plan name -->
                    <appServicePlanName>java-functions-app-service-plan</appServicePlanName>
                    <!-- function app region-->
                    <!-- refers https://github.com/microsoft/azure-maven-plugins/wiki/Azure-Functions:-Configuration-Details#supported-regions for all valid values -->
                    <region>westus</region>
                    <!-- function pricingTier, default to be consumption if not specified -->
                    <!-- refers https://github.com/microsoft/azure-maven-plugins/wiki/Azure-Functions:-Configuration-Details#supported-pricing-tiers for all valid values -->
                    <!-- <pricingTier></pricingTier> -->
                    
                    <!-- Whether to disable application insights, default is false -->
                    <!-- refers https://github.com/microsoft/azure-maven-plugins/wiki/Azure-Functions:-Configuration-Details for all valid configurations for application insights-->
                    <!-- <disableAppInsights></disableAppInsights> -->
                    <runtime>
                        <!-- runtime os, could be windows, linux or docker-->
                        <os>windows</os>
                        <javaVersion>8</javaVersion>
                        <!-- for docker function, please set the following parameters -->
                        <!-- <image>[hub-user/]repo-name[:tag]</image> -->
                        <!-- <serverId></serverId> -->
                        <!-- <registryUrl></registryUrl>  -->
                    </runtime>
                    <appSettings>
                        <property>
                            <name>FUNCTIONS_EXTENSION_VERSION</name>
                            <value>~3</value>
                        </property>
                    </appSettings>
                </configuration>
```


デプロイする前には、正しくパッケージングしましょう。細かな話を言うと、`azure-functions:package` がパッケージングのゴールですが、シンプルに`package` で、必要なJSONファイルなどを生成してくれます。`target/azure-functions` が デプロイ用の成果物が格納されたディレクトリになります。各トリガの情報は、`functions.json` に自動生成されます。

```json
{
  "scriptFile" : "../functions-sample-1.0-SNAPSHOT.jar",
  "entryPoint" : "com.example.moris.Function.run",
  "bindings" : [ {
    "type" : "httpTrigger",
    "direction" : "in",
    "name" : "req",
    "methods" : [ "GET", "POST" ],
    "authLevel" : "ANONYMOUS"
  }, {
    "type" : "http",
    "direction" : "out",
    "name" : "$return"
  } ]
}
```

デプロイはするには、以下のコマンドを実行します。あらかじめ、`az login` しておいてください。

```
mvn azure-functions:deploy
```

# ワーカーやライブラリの依存関係の話

トリガーを具体的に呼び出すのは Azure Functions Java Worker です。ソースはGithubで公開されています。Java Functions 上での怪しい挙動などは、ここにIssueを投げても良いでしょう（サポートでもいいですけど）

* https://github.com/Azure/azure-functions-java-worker

また、Java8とJava11で、クラスロードの優先順位が変わるので注意が必要です。

Java8では、ワーカー側のJarを読み込んで、次にクライアント側のJarを読み込みますが、Java11では、常にクライアント側のJarを読み込みます。Java8でこの挙動を変更したいときは、環境変数 `FUNCTIONS_WORKER_JAVA_LOAD_APP_LIBS ` を  `1` or `True` に設定してください。

https://github.com/Azure/azure-functions-java-worker/issues/381

**とくに、FunctionsのランタイムにJava8を選択したとき、 Azure SDK for Java との相性が悪いので 例外が出たり、ハングしてタイムアウトする場合があるので注意が必要です。**


# まとめ

WebApps のほうは、`azure-webapp` なのに、Functions の方は、`azure-functions` でいつもプラグイン名が混乱します。単複系は難しい。