javaでAzure functions

# 概要
azure functionsで、正式にjavaがサポートされたらしいので、ちょっとやってみます。
基本的には、下記の流れ通りでできましたが、途中何回かつまずいたので、つまずき内容を含めメモしておきます。
[Java と Maven を使用して初めての関数を作成する (プレビュー)](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-create-first-java-maven)

# 環境
- windows10
- eclipse
 
# まずはいろいろインストール
## .NET CORE
↓からインストールします。
https://www.microsoft.com/net/learn/get-started/windows
## node.js
### インストーラ
『Windows Installer (.msi)』を選ぶ。
https://nodejs.org/en/download/
### 確認
インストールしたら、既にpathが通っている。
コマンドプロンプトで確認しよう。

```console
> node -v
v8.9.4
```
## Azure Functions Core Tools
Azure functionsをローカルで実行したり、デバッグしたりできるツールです。
node.jsをインストールしたので、npmを使ってインストールします。

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

```
C:\Users\xxx\AppData\Roaming\npm\func -> C:\Users\xxx\AppData\Roaming\npm\node_modules\azure-functions-core-tools\lib\main.js
C:\Users\xxx\AppData\Roaming\npm\azurefunctions -> C:\Users\xxx\AppData\Roaming\npm\node_modules\azure-functions-core-tools\lib\main.js
C:\Users\xxx\AppData\Roaming\npm\azfun -> C:\Users\xxx\AppData\Roaming\npm\node_modules\azure-functions-core-tools\lib\main.js

azure-functions-core-tools@2.0.1-beta.22 postinstall C:\Users\xxx\AppData\Roaming\npm\node_modules\azure-functions-core-tools
node lib/install.js

[==================] Downloading Azure Functions Cli
+ azure-functions-core-tools@2.0.1-beta.22
added 46 packages in 8.164s
```

## Azure CLI
名前の通り、Azureをコマンドラインで色々できるツールです。
下記を参考にインストール。
https://docs.microsoft.com/ja-jp/cli/azure/install-azure-cli-windows
# プロジェクトを作成
コマンドプロンプトでeclipseのworkspaceに移り、下記コマンドを実行。
GUIで新規プロジェクトを作ってもいいと思います。

```
mvn archetype:generate -DarchetypeGroupId=com.microsoft.azure -DarchetypeArtifactId=azure-functions-archetype
```
groupId等を色々聞いてくるので、好きな名前をつけよう。

```
Define value for property 'groupId': jp.suzuq
Define value for property 'artifactId': azure.functions
Define value for property 'version' 1.0-SNAPSHOT: : 0.0.1
Define value for property 'package' jp.suzuq: : jp.suzuq.azure.functions
Define value for property 'appName' azure.functions-20180222185249620: :
Define value for property 'appRegion' westus: : jp
```

# さっそくビルド
プロジェクトができた段階で、簡単なfunctionクラスが作成されています。
まずはこれをビルド&デプロイして動作確認してみましょう。

```
mvn clean package
```
そのままやったら、下記のエラーでビルドが失敗した。
>Failed to execute goal com.microsoft.azure:azure-functions-maven-plugin:0.1.10:package (package-functions) on project azure.functions: Execution package-functions of goal com.microsoft.azure:azure-functions-maven-plugin:0.1.10:package failed: A required class was missing while executing com.microsoft.azure:azure-functions-maven-plugin:0.1.10: package: com/microsoft/azure/serverless/functions/annotation/DocumentDBInput

azure-functions-maven-pluginが何かマズいみたいなので、バージョンを最新の0.2.1に変えてみたらうまくいきました。

```pom.xml
                <plugin>
                    <groupId>com.microsoft.azure</groupId>
                    <artifactId>azure-functions-maven-plugin</artifactId>
                    <version>0.2.1</version>
                </plugin>

```

# ローカルでテスト実行
Azureにデプロイする前に、ローカルで動かしてみます。

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

コンソールに、functionsのロゴが表示されて…

```
[INFO] Starting running Azure Functions...

                  %%%%%%
                 %%%%%%
            @   %%%%%%    @
          @@   %%%%%%      @@
       @@@    %%%%%%%%%%%    @@@
     @@      %%%%%%%%%%        @@
       @@         %%%%       @@
         @@      %%%       @@
           @@    %%      @@
                %%
                %
```

下記の表示が出たら準備完了。

```
Http Functions:

	hello: http://localhost:7071/api/hello
```

指示された通り、curlで下記を実行してみます。
curlがなければ、何かhttpを投げられるツールを使ってください。

```
curl -w '\n' -d hoge http://localhost:7071/api/hello
```

コンソールにログが出力され、実行されている事がわかります。

```
[2018/05/02 9:51:28] Function started (Id=0aed5288-e5ff-47e2-868b-8455d7731b70)
[2018/05/02 9:51:28] Executing 'Functions.hello' (Reason='This function was programmatically called via the host APIs.', Id=0aed5288-e5ff-47e2-868b-8455d7731b70)
[2018/05/02 9:51:28] Java HTTP trigger processed a request.
[2018/05/02 9:51:28] Function "hello" (ID: 40832889-9057-4664-885e-c84407660a4a) invoked by Java Worker
[2018/05/02 9:51:28] Function completed (Success, Id=0aed5288-e5ff-47e2-868b-8455d7731b70, Duration=5ms)
[2018/05/02 9:51:28] Executed 'Functions.hello' (Succeeded, Id=0aed5288-e5ff-47e2-868b-8455d7731b70)
```

200番のレスポンスとともに、実行結果が返されました。

```
Hello, hoge'
```

# デプロイ
いよいよデプロイしてみます。

## azureにログイン
azure CLIを使ってログインしておきます。
PCでpowershellを開いて「az login」。

```powershell
> az login
To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code XXXXXXXXX to authenticate.
```

指示された通り、https://microsoft.com/devicelogin へ行ってpowershellのコンソールに表示されたコードを入力。
「az login」コマンドが終了して、ログインしたazureの情報が示されます。

```powershell
[
  {
    "cloudName": "AzureCloud",
    "id": "xxxxxxxxxxxxxxxxxxxxxxx",
    "isDefault": true,
    "name": "Microsoft Azure",
    "state": "Enabled",
    "tenantId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "user": {
      "name": "xxxx@xxxxxxxxxxxxxxxxxxxxxx",
      "type": "user"
    }
  }
]
```

## mavenでデプロイ
eclipseに戻って、下記mavenコマンドを実行します。

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

なんかエラー出た…。

```
[ERROR] Failed to execute goal com.microsoft.azure:azure-functions-maven-plugin:0.2.1:deploy (default-cli) on project azure.functions: Status code 400, {"error":{"code":"LocationNotAvailableForResourceGroup","message":"The provided location 'jp' is not available for resource group. List of available regions is 'centralus,eastasia,southeastasia,eastus,eastus2,westus,westus2,northcentralus,southcentralus,westcentralus,northeurope,westeurope,japaneast,japanwest,brazilsouth,australiasoutheast,australiaeast,westindia,southindia,centralindia,canadacentral,canadaeast,uksouth,ukwest,koreacentral,koreasouth,francecentral'."}}
```

regionがどうこう言ってるので、pom.xmlのregionっぽい設定を「jp」から「japaneast」に変えてみる。

```pom.xml
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <functionAppName>azure.functions-9999999.azurewebsites.net</functionAppName>
        <functionAppRegion>japaneast</functionAppRegion>
    </properties>
```

今度は違うエラーが。

```
[ERROR] Failed to execute goal com.microsoft.azure:azure-functions-maven-plugin:0.2.1:deploy (default-cli) on project azure.functions: The host name azure.functions-20180222185249620.azurewebsites.net is invalid. OnError while emitting onNext value: retrofit2.Response.class
```

function名を変えてみる。
function名は、azureにデプロイされるjarのファイル名にもなるので、ここでもう一度ビルドしておく。

```pom.xml
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <functionAppName>testFunction</functionAppName>
        <functionAppRegion>japaneast</functionAppRegion>
    </properties>
```

またエラー。

```
[ERROR] Failed to execute goal com.microsoft.azure:azure-functions-maven-plugin:0.2.1:deploy (default-cli) on project azure.functions: {
[ERROR] "message": "An error has occurred.",
[ERROR] "exceptionMessage": "An error occurred when trying to create a controller of type 'DeploymentController'. Make sure that the controller has a parameterless public constructor.",
[ERROR] "exceptionType": "System.InvalidOperationException",
[ERROR] "stackTrace": "   at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)\r\n   at System.Web.Http.Controllers.HttpControllerDescriptor.CreateController(HttpRequestMessage request)\r\n   at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()",
[ERROR] "innerException": {
[ERROR] "message": "An error has occurred.",
[ERROR] "exceptionMessage": "The user name or password is incorrect.\r\n",
[ERROR] "exceptionType": "System.IO.IOException",
[ERROR] "stackTrace": "   at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)\r\n   at System.IO.Directory.InternalCreateDirectory(String fullPath, String path, Object dirSecurityObj, Boolean checkHost)\r\n   at System.IO.Directory.InternalCreateDirectoryHelper(String path, Boolean checkHost)\r\n   at System.IO.Directory.CreateDirectory(String path)\r\n   at System.IO.Abstractions.DirectoryWrapper.CreateDirectory(String path)\r\n   at Microsoft.Web.Deployment.WebApi.PathHelper.GetLogFilesFolderPath(IEnvironment environment)\r\n   at Microsoft.Web.Deployment.WebApi.DeploymentController..ctor()\r\n   at lambda_method(Closure )\r\n   at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"
[ERROR] }
[ERROR] }: OnError while emitting onNext value: retrofit2.Response.class
```

ちょっと何言ってるかわかんないので、そのままもう一度やってみたら、なぜかSUCCESS。

```
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 35.832 s
[INFO] Finished at: 2018-05-10T18:55:38+09:00
[INFO] Final Memory: 48M/551M
[INFO] ------------------------------------------------------------------------
```
azure portalでデプロイしたfunctionを確認しよう。
サイドメニューの「Function App」から。

![azurePortal.png](https://qiita-image-store.s3.amazonaws.com/0/230422/be2d1c0b-3dbe-3158-6ccb-d412a95d5c59.png)

# 実行してみる
↑の画像の右上「関数のURLの取得」からURLをコピーできる。
コピーしたURLにcurlからリクエストを投げてみましょう。

```
curl -w '\n' https://functionName.azurewebsites.net/api/hello -d AzureFunctions
Hello, AzureFunctions
```

無事動きました!

# オリジナルのfunctionを作る
とりあえずデフォルトで用意されたfunctionは動いたので、オリジナルのやつを作っていきましょう。
さっき実行したのは、workspaceに自動生成されたこのプログラム。
これを参考に、自分の関数を作っていきます。

```java:Function.java
package jp.suzuq.azure.functions;

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

/**
 * Azure Functions with HTTP Trigger.
 */
public class Function {
    /**
     * This function listens at endpoint "/api/hello". Two ways to invoke it using "curl" command in bash:
     * 1. curl -d "HTTP Body" {your host}/api/hello
     * 2. curl {your host}/api/hello?name=HTTP%20Query
     */
    @FunctionName("hello")
    public HttpResponseMessage<String> hello(
            @HttpTrigger(name = "req", methods = {"get", "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.createResponse(400, "Please pass a name on the query string or in the request body");
        } else {
            return request.createResponse(200, "Hello, " + name);
        }
    }
}
```

## タイマートリガーを作る
デフォルトのは、httpリクエストがトリガーとなって動くfunctionだったので、タイマーがトリガーとなって動くのを作ってみます。

```java:TimerFunction.java
package jp.suzuq.azure.functions;

import com.microsoft.azure.serverless.functions.ExecutionContext;
import com.microsoft.azure.serverless.functions.annotation.FunctionName;
import com.microsoft.azure.serverless.functions.annotation.TimerTrigger;

public class TimerFunction {
    @FunctionName("timer")
    public void timer(
            @TimerTrigger(name = "timer", schedule = "0 */5 * * * *") String timerInfo,
            final ExecutionContext context) 
    {
        context.getLogger().info("timerInfo : " + timerInfo);

    }
}
```

メソッドの第一引数に@TimerTriggerアノテーションをつけてあげます。
んで、「schedule」にcron形式でスケジュールを記載します。これがfunctionの実行スケジュールになります。

## ライブラリを追加
なんでjavaでAzure functionsを作りたかったかって、javaの色んなライブラリを使いたかったから。
社内mavenリポジトリに、javaの便利クラスがたくさんあって、普段の開発で使っているので、Azure functionsでも使えたらいいな、と思ったのです。

pom.xmlに追加してデプロイしてみます。

```pom.xml
		<dependency>
			<groupId>jp.hoge</groupId>
			<artifactId>HogeCommon</artifactId>
			<version>1.20.0</version>
		</dependency>
```
動かしてみると、なぜかfunctionがタイムアウトになり、途中で強制終了してしまいます。
なぜタイムアウト?と色々悩みましたが、結局のところpomに追加したライブラリが、ビルド済モジュールに含まれていなかったようです。ならClassNotFoundExceptionを出してくれよ…

ビルド済モジュールは、eclipse workspaceのtargetフォルダを見るか、実際にデプロイされたものを見たければAzure portalからKuduでダウンロードできます。

![スケッチ.png](https://qiita-image-store.s3.amazonaws.com/0/230422/22b094e3-ce28-6a28-7f33-b11a0a407bde.png)

![スケッチ2.png](https://qiita-image-store.s3.amazonaws.com/0/230422/bc3aeeb5-8100-5954-ca3b-b8ce103022f0.png)


ビルドのしかたが悪かったという事で、下記を参考にpom.xmlを修正しました。
[How to add dependency JAR in java azure functions
](https://stackoverflow.com/questions/47168154/how-to-add-dependency-jar-in-java-azure-functions)

```pom.xml
			<plugin>
			   <groupId>org.apache.maven.plugins</groupId>
			   <artifactId>maven-assembly-plugin</artifactId>
			   <configuration>
			      <appendAssemblyId>false</appendAssemblyId>
			      <descriptorRefs>
			         <descriptorRef>jar-with-dependencies</descriptorRef>
			      </descriptorRefs>
			      <archive />
			   </configuration>
			   <executions>
			      <execution>
			         <id>make-assembly</id>
			         <phase>package</phase>
			         <goals>
			            <goal>single</goal>
			         </goals>
			      </execution>
			   </executions>
			</plugin>
```

わ～い!動いた!
![1528452015300.png](https://qiita-image-store.s3.amazonaws.com/0/230422/26ed634e-c6b1-5596-df7d-863e50ee1ef4.png)

まだまだ色々試してみたいですが、とりあえずここまで。

# 感想
社内ライブラリをAzure Functionsで使う事ができて満足。
でもやっぱ色々と準備するのが面倒ですね。
簡単なfunctionなら、やっぱjavascriptで作るのが一番簡単。
javaは今回のように社内の既存資産を使いまわしたり、ローカルでデバッグできるのがいいですね。
用途によって使い分けたいと思います。

# 参考になったサイト
[Java と Maven を使用して初めての関数を作成する (プレビュー)](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-create-first-java-maven)
[javaでトリガーを設定する](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-reference-java)
[Azure CLIインストール＆ログイン（Windows版）](https://qiita.com/miwato/items/9ca2ab2134e309891f55)
[How to add dependency JAR in java azure functions
](https://stackoverflow.com/questions/47168154/how-to-add-dependency-jar-in-java-azure-functions)