JavaでAzure Functionsを作る

Microsoftのドキュメント[「Java と Maven を使用して初めての関数を作成する (プレビュー)」](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-create-first-java-maven)を以下の環境で試しました。

- Windows 10 Pro 1803(64bit)
- JDK 8u152
- Microsoft .NET Core SDK 2.1.402
- Azure CLI 2.0.45
- Visual Studio Code 1.27.1
    - Java Extension Pack 0.3.0
    - Language Support for Java(TM) by Red Hat 0.30.0
    - Maven for Java 0.10.0
- Node.js 8.9.4 (自分の環境ではプリインストール）
- npm 5.6.0（自分の環境ではプリインストール）

無事に動いたので以下メモ。

# Azure Functions Core Toolsのインストール

[ドキュメント](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-run-local#v2)を参考にAzure Functions Core Toolsをインストールしました。手順通り、まずは.NET Coreをインストールしました。

[.NET Coreのダウンロードページ](https://www.microsoft.com/net/download)で.NET Core SDKをダウンロード
![2018-09-16_12h36_38.png](https://qiita-image-store.s3.amazonaws.com/0/47737/a29d0d14-af53-c36c-d054-212afc4e12dc.png)

インストールを進めます。
![2018-09-16_12h39_37.png](https://qiita-image-store.s3.amazonaws.com/0/47737/0703add9-dae4-c316-687d-f626e93b8af2.png)

インストール完了です。
![2018-09-16_12h45_16.png](https://qiita-image-store.s3.amazonaws.com/0/47737/6f5caaca-86fb-d2a9-5209-6cc82099f0cb.png)

npmでAzure Functions Core Toolsをインストールします。自分の環境は既にインストール済だったnpm 5.6.0を利用しました。

```
npm install -g azure-functions-core-tools@core
```

# azure-functions-archetypeで雛形を作成

Mavenの[「azure-functions-archetype」](https://github.com/Microsoft/azure-maven-archetypes)を使って雛形を作成します。

VSCodeで**「表示」**->**「コマンドパレット」**から**「Maven:Generate from Maven Archetype」**を選択して、保存先のフォルダを選択します。
![2018-09-17_21h09_55.png](https://qiita-image-store.s3.amazonaws.com/0/47737/0fe3be57-8504-aece-cb33-630c3c218cdd.png)

**「azure-functions-archetype」**を選択します。
![2018-09-17_21h11_09.png](https://qiita-image-store.s3.amazonaws.com/0/47737/3efb85ef-925d-5964-4915-29ee386ea169.png)

すると自動的に以下の処理が開始されます。

```
PS D:\web\azure\AzureFunctionsJava> cmd /c mvn archetype:generate -DarchetypeArtifactId="azure-functions-archetype" -DarchetypeGroupId="com.microsoft.azure"
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------< org.apache.maven:standalone-pom >-------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] --------------------------------[ pom ]---------------------------------
[INFO]
[INFO] >>> maven-archetype-plugin:3.0.1:generate (default-cli) > generate-sources @ standalone-pom >>>
[INFO]
[INFO] <<< maven-archetype-plugin:3.0.1:generate (default-cli) < generate-sources @ standalone-pom <<<
[INFO]
[INFO]
[INFO] --- maven-archetype-plugin:3.0.1:generate (default-cli) @ standalone-pom ---
[INFO] Generating project in Interactive mode
[INFO] Archetype [com.microsoft.azure:azure-functions-archetype:1.15] found in catalog remote
Downloading from central: https://repo.maven.apache.org/maven2/com/microsoft/azure/azure-functions-archetype/1.15/azure-functions-archetype-1.15.pom
Downloaded from central: https://repo.maven.apache.org/maven2/com/microsoft/azure/azure-functions-archetype/1.15/azure-functions-archetype-1.15.pom (1.5 kB at 4.3 kB/s)
Downloading from central: https://repo.maven.apache.org/maven2/com/microsoft/azure/azure-functions-archetype/1.15/azure-functions-archetype-1.15.jar
Downloaded from central: https://repo.maven.apache.org/maven2/com/microsoft/azure/azure-functions-archetype/1.15/azure-functions-archetype-1.15.jar (15 kB at 44 kB/s)
```

対話式に設定します。ここではgroupIdとartifactIdだけ指定して、あとはデフォルトで空白のまま進みました。

```
Define value for property 'groupId' (should match expression '[A-Za-z0-9_\-\.]+'): tech.kikutaro
[INFO] Using property: groupId = tech.kikutaro
Define value for property 'artifactId' (should match expression '[A-Za-z0-9_\-\.]+'): AzureFunctionsJava
[INFO] Using property: artifactId = AzureFunctionsJava
Define value for property 'version' 1.0-SNAPSHOT: :
Define value for property 'package' tech.kikutaro: :
Define value for property 'appName' azurefunctionsjava-20180917211315850: :
Define value for property 'appRegion' westus: :
Define value for property 'resourceGroup' java-functions-group: :
Confirm properties configuration:
groupId: tech.kikutaro
groupId: tech.kikutaro
artifactId: AzureFunctionsJava
artifactId: AzureFunctionsJava
version: 1.0-SNAPSHOT
package: tech.kikutaro
appName: azurefunctionsjava-20180917211315850
appRegion: westus
resourceGroup: java-functions-group
 Y: : Y
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: azure-functions-archetype:1.15
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: tech.kikutaro
[INFO] Parameter: artifactId, Value: AzureFunctionsJava
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: package, Value: tech.kikutaro
[INFO] Parameter: packageInPathFormat, Value: tech/kikutaro
[INFO] Parameter: appName, Value: azurefunctionsjava-20180917211315850
[INFO] Parameter: resourceGroup, Value: java-functions-group
[INFO] Parameter: package, Value: tech.kikutaro
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: groupId, Value: tech.kikutaro
[INFO] Parameter: appRegion, Value: westus
[INFO] Parameter: artifactId, Value: AzureFunctionsJava
[INFO] Project created from Archetype in dir: D:\web\azure\AzureFunctionsJava\AzureFunctionsJava
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 02:20 min
[INFO] Finished at: 2018-09-17T21:13:37+09:00
[INFO] ------------------------------------------------------------------------
```

これで雛形の完成です。雛形といいつつ、以下のようなコードが書かれているので、そのまま動かせます。

```java
package tech.kikutaro;

import java.util.*;
import com.microsoft.azure.functions.annotation.*;
import com.microsoft.azure.functions.*;

/**
 * Azure Functions with HTTP Trigger.
 */
public class Function {
    /**
     * This function listens at endpoint "/api/HttpTrigger-Java". Two ways to invoke it using "curl" command in bash:
     * 1. curl -d "HTTP Body" {your host}/api/HttpTrigger-Java
     * 2. curl {your host}/api/HttpTrigger-Java?name=HTTP%20Query
     */
    @FunctionName("HttpTrigger-Java")
    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
        String query = request.getQueryParameters().get("name");
        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();
        }
    }
}
```

# ローカルでの動作確認

ビルドします。

```
mvn clean package
```

以下、ダウンロード部分は一部省略しています。

```
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------< tech.kikutaro:AzureFunctionsJava >------------------
[INFO] Building Azure Java Functions 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ AzureFunctionsJava ---
[INFO] Deleting D:\web\azure\AzureFunctionsJava\AzureFunctionsJava\target
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:resources (default-resources) @ AzureFunctionsJava ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory D:\web\azure\AzureFunctionsJava\AzureFunctionsJava\src\main\resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ AzureFunctionsJava ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to D:\web\azure\AzureFunctionsJava\AzureFunctionsJava\target\classes
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:testResources (default-testResources) @ AzureFunctionsJava ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory D:\web\azure\AzureFunctionsJava\AzureFunctionsJava\src\test\resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ AzureFunctionsJava ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 2 source files to D:\web\azure\AzureFunctionsJava\AzureFunctionsJava\target\test-classes
[WARNING] /D:/web/azure/AzureFunctionsJava/AzureFunctionsJava/src/test/java/tech/kikutaro/FunctionTest.java: D:\web\azure\AzureFunctionsJava\AzureFunctionsJava\src\test\java\tech\kikutaro\FunctionTest.javaの操作は、未チェックまたは安全ではありません。
[WARNING] /D:/web/azure/AzureFunctionsJava/AzureFunctionsJava/src/test/java/tech/kikutaro/FunctionTest.java: 詳細は、-Xlint:uncheckedオプションを指定して再コン
パイルしてください。
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ AzureFunctionsJava ---
[INFO] Surefire report directory: D:\web\azure\AzureFunctionsJava\AzureFunctionsJava\target\surefire-reports

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running tech.kikutaro.FunctionTest
9 17, 2018 9:25:20 午後 tech.kikutaro.Function run
情報: Java HTTP trigger processed a request.
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.302 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO]
[INFO] --- maven-dependency-plugin:3.1.1:copy-dependencies (copy-dependencies) @ AzureFunctionsJava ---
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ AzureFunctionsJava ---
[INFO] Building jar: D:\web\azure\AzureFunctionsJava\AzureFunctionsJava\target\AzureFunctionsJava-1.0-SNAPSHOT.jar
[INFO]
[INFO] --- azure-functions-maven-plugin:1.0.0-beta-6:package (package-functions) @ AzureFunctionsJava ---
Downloading from central: https://repo.maven.apache.org/maven2/com/microsoft/azure/azure-maven-plugin-lib/1.2.0/azure-maven-plugin-lib-1.2.0.pom
AI: INFO 17-09-2018 21:27, 1: Configuration file has been successfully found as resource
AI: INFO 17-09-2018 21:27, 1: Configuration file has been successfully found as resource
[INFO]
[INFO] Step 1 of 7: Searching for Azure Functions entry points
[INFO] 1 Azure Functions entry point(s) found.
[INFO]
[INFO] Step 2 of 7: Generating Azure Functions configurations
[INFO] Generation done.
[INFO]
[INFO] Step 3 of 7: Validating generated configurations
[INFO] Validation done.
[INFO]
[INFO] Step 4 of 7: Saving empty host.json
[INFO] Successfully saved to D:\web\azure\AzureFunctionsJava\AzureFunctionsJava\target\azure-functions\azurefunctionsjava-20180917211315850\host.json
[INFO]
[INFO] Step 5 of 7: Saving configurations to function.json
[INFO] Starting processing function: HttpTrigger-Java
[INFO] Successfully saved to D:\web\azure\AzureFunctionsJava\AzureFunctionsJava\target\azure-functions\azurefunctionsjava-20180917211315850\HttpTrigger-Java\function.json
[INFO]
[INFO] Step 6 of 7: Copying JARs to staging directoryD:\web\azure\AzureFunctionsJava\AzureFunctionsJava\target\azure-functions\azurefunctionsjava-20180917211315850
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource to D:\web\azure\AzureFunctionsJava\AzureFunctionsJava\target\azure-functions\azurefunctionsjava-20180917211315850
[INFO] Copied successfully.
[INFO]
[INFO] Step 7 of 7: Installing function extensions if needed
  Writing C:\Users\kikuta\AppData\Local\Temp\tmpC1CE.tmp
info : パッケージ 'Microsoft.Azure.WebJobs.Script.ExtensionsMetadataGenerator' の PackageReference をプロジェクト 'D:\web\azure\AzureFunctionsJava\AzureFunctionsJava\extensions.csproj' に追加しています。
log  : D:\web\azure\AzureFunctionsJava\AzureFunctionsJava\extensions.csproj のパッケージを復元しています...
info : パッケージ 'Microsoft.Azure.WebJobs.Script.ExtensionsMetadataGenerator' は、プロジェクト 'D:\web\azure\AzureFunctionsJava\AzureFunctionsJava\extensions.csproj' のすべての指定されたフレームワークとの互換性があります。
info : パッケージ 'Microsoft.Azure.WebJobs.Script.ExtensionsMetadataGenerator' バージョン '1.0.0' の PackageReference がファイル 'D:\web\azure\AzureFunctionsJava\AzureFunctionsJava\extensions.csproj' で更新されました。


.NET Core 向け Microsoft (R) Build Engine バージョン 15.8.166+gd4e8d81a88
Copyright (C) Microsoft Corporation.All rights reserved.

  D:\web\azure\AzureFunctionsJava\AzureFunctionsJava\extensions.csproj のパッケージを復元しています...
  MSBuild ファイル D:\web\azure\AzureFunctionsJava\AzureFunctionsJava\obj\extensions.csproj.nuget.g.props を生成しています。
  MSBuild ファイル D:\web\azure\AzureFunctionsJava\AzureFunctionsJava\obj\extensions.csproj.nuget.g.targets を生成しています。
  D:\web\azure\AzureFunctionsJava\AzureFunctionsJava\extensions.csproj の復元が 971.41 ms で完了しました。
  extensions -> D:\web\azure\AzureFunctionsJava\AzureFunctionsJava\target\azure-functions\azurefunctionsjava-20180917211315850\bin\extensions.dll

ビルドに成功しました。
    0 個の警告
    0 エラー

経過時間 00:00:17.67


[INFO] Function extension installation done.
[INFO] Successfully built Azure Functions.
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:copy-resources (copy-resources) @ AzureFunctionsJava ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 2 resources
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 03:07 min
[INFO] Finished at: 2018-09-17T21:28:15+09:00
[INFO] ------------------------------------------------------------------------
```

実行します。

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

```
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------< tech.kikutaro:AzureFunctionsJava >------------------
[INFO] Building Azure Java Functions 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- azure-functions-maven-plugin:1.0.0-beta-6:run (default-cli) @ AzureFunctionsJava ---
AI: INFO 17-09-2018 22:00, 1: Configuration file has been successfully found as resource
AI: INFO 17-09-2018 22:00, 1: Configuration file has been successfully found as resource
[INFO] Azure Function App's staging directory found at: D:\web\azure\AzureFunctionsJava\AzureFunctionsJava\target\azure-functions\azurefunctionsjava-20180917211315850
[INFO] Azure Functions Core Tools found.

                  %%%%%%
                 %%%%%%
            @   %%%%%%    @
          @@   %%%%%%      @@
       @@@    %%%%%%%%%%%    @@@
     @@      %%%%%%%%%%        @@
       @@         %%%%       @@
         @@      %%%       @@
           @@    %%      @@
                %%
                %

Azure Functions Core Tools (2.0.1-beta.38)
Function Runtime Version: 2.0.12050.0
Skipping 'AzureWebJobsStorage' from local settings as it's already defined in current environment variables.
Skipping 'AzureWebJobsDashboard' from local settings as it's already defined in current environment variables.
info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[0]
      User profile is available. Using 'C:\Users\kikuta\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest.
info: Host.Startup[0]
      Reading host configuration file 'D:\web\azure\AzureFunctionsJava\AzureFunctionsJava\target\azure-functions\azurefunctionsjava-20180917211315850\host.json'info: Host.Startup[0]
      Host configuration file read:
      {
        "version": "2.0"
      }
[2018/09/17 13:00:43] Initializing Host.
[2018/09/17 13:00:43] Host initialization: ConsecutiveErrors=0, StartupCount=1
[2018/09/17 13:00:44] Starting JobHost
[2018/09/17 13:00:44] Starting Host (HostId=xxxxxxxxx-xxxxxxxx, InstanceId=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, Version=2.0.12050.0, ProcessId=11372, AppDomainId=1, Debug=False, FunctionsExtensionVersion=)
[2018/09/17 13:00:44] Starting language worker process:C:\Program Files\Java\jdk1.8.0_152\bin\java  -jar "C:\Users\kikuta\AppData\Roaming\npm\node_modules\azure-functions-core-tools\bin\workers\java\azure-functions-java-worker.jar" --host 127.0.0.1 --port 36887 --workerId xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --requestId xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --grpcMaxMessageLength 134217728
[2018/09/17 13:00:44] C:\Program Files\Java\jdk1.8.0_152\bin\java process with Id=26080 started
[2018/09/17 13:00:44] Generating 1 job function(s)
[2018/09/17 13:00:44] Found the following functions:
[2018/09/17 13:00:44] Host.Functions.HttpTrigger-Java
[2018/09/17 13:00:44]
[2018/09/17 13:00:44] Host initialized (678ms)
[2018/09/17 13:00:44] Host started (695ms)
[2018/09/17 13:00:44] Job host started
Hosting environment: Production
Content root path: D:\web\azure\AzureFunctionsJava\AzureFunctionsJava\target\azure-functions\azurefunctionsjava-20180917211315850
Now listening on: http://0.0.0.0:7071
Application started. Press Ctrl+C to shut down.
Listening on http://0.0.0.0:7071/
Hit CTRL-C to exit...

Http Functions:

        HttpTrigger-Java: http://localhost:7071/api/HttpTrigger-Java

[2018/09/17 13:00:45] Microsoft Azure Functions Java Runtime [build 1.1.0-beta9]
[2018/09/17 13:00:49] [INFO] {MessageHandler.handle}: Message generated by "StartStream.Builder"
[2018/09/17 13:00:49] Host lock lease acquired by instance ID '0000000000000000000000000xxxxxxx'.
[2018/09/17 13:00:57] Worker initialized
[2018/09/17 13:00:57] "HttpTrigger-Java" loaded (ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, Reflection: "D:\web\azure\AzureFunctionsJava\AzureFunctionsJava\target\azure-functions\azurefunctionsjava-20180917211315850\AzureFunctionsJava-1.0-SNAPSHOT.jar"::"tech.kikutaro.Function.run")
```

「 http://localhost:7071 」にアクセスすると以下表示されました。
![2018-09-16_13h13_14.png](https://qiita-image-store.s3.amazonaws.com/0/47737/bfbd8b84-1686-eaf4-74c6-c748244b6af1.png)

さらに「 http://localhost:7071/api/HttpTrigger-Java?name=kikutaro 」で以下の表示を確認しました。
![2018-09-16_13h14_27.png](https://qiita-image-store.s3.amazonaws.com/0/47737/f682692e-ae90-e072-dae6-8443ee0a05bc.png)

# Microsoft Azureへデプロイ

ローカルで動作確認ができたので、最後にAzureへデプロイします。まずはログインから。

```
az login
```

デプロイします。

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

```
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------< tech.kikutaro:AzureFunctionsJava >------------------
[INFO] Building Azure Java Functions 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- azure-functions-maven-plugin:1.0.0-beta-6:deploy (default-cli) @ AzureFunctionsJava ---
AI: INFO 17-09-2018 22:07, 1: Configuration file has been successfully found as resource
AI: INFO 17-09-2018 22:07, 1: Configuration file has been successfully found as resource
[INFO] Authenticate with Azure CLI 2.0
[INFO] The specified function app does not exist. Creating a new function app...
[INFO] Successfully created the function app: azurefunctionsjava-20180917211315850
[INFO] Trying to deploy the function app...
[INFO] Successfully deployed the function app at https://azurefunctionsjava-20180917211315850.azurewebsites.net
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 01:59 min
[INFO] Finished at: 2018-09-17T22:09:03+09:00
[INFO] ------------------------------------------------------------------------
```

Azure Portalで確認するとMavenで指定した名前のリソースグループが作成されています。
![2018-09-17_22h09_57.png](https://qiita-image-store.s3.amazonaws.com/0/47737/9cbf9cc8-bcf6-331d-45d3-adca9a7d1e46.png)

作成されたFunctionをクリックするとURLが確認できます。
![2018-09-17_22h08_29.png](https://qiita-image-store.s3.amazonaws.com/0/47737/92648fe2-26f8-baea-4f7a-208f9ee17156.png)

ローカルと同じように確認します。まずはトップURL。
![2018-09-17_22h14_58.png](https://qiita-image-store.s3.amazonaws.com/0/47737/ff436052-6967-9a1c-b40d-de761ef47d08.png)

ローカルと同じ挙動です。
![2018-09-17_22h14_25.png](https://qiita-image-store.s3.amazonaws.com/0/47737/f1a63b66-70e0-ffe2-9ee4-dcb4208c063c.png)

思ってたより簡単でした。