#概要
azure functionsで、正式にjavaがサポートされたらしいので、ちょっとやってみます。
基本的には、下記の流れ通りでできましたが、途中何回かつまずいたので、つまずき内容を含めメモしておきます。
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が通っている。
コマンドプロンプトで確認しよう。
> 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に変えてみたらうまくいきました。
<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」。
> 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の情報が示されます。
[
{
"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」に変えてみる。
<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のファイル名にもなるので、ここでもう一度ビルドしておく。
<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」から。
#実行してみる
↑の画像の右上「関数のURLの取得」からURLをコピーできる。
コピーしたURLにcurlからリクエストを投げてみましょう。
curl -w '\n' https://functionName.azurewebsites.net/api/hello -d AzureFunctions
Hello, AzureFunctions
無事動きました!
#オリジナルのfunctionを作る
とりあえずデフォルトで用意されたfunctionは動いたので、オリジナルのやつを作っていきましょう。
さっき実行したのは、workspaceに自動生成されたこのプログラム。
これを参考に、自分の関数を作っていきます。
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だったので、タイマーがトリガーとなって動くのを作ってみます。
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に追加してデプロイしてみます。
<dependency>
<groupId>jp.hoge</groupId>
<artifactId>HogeCommon</artifactId>
<version>1.20.0</version>
</dependency>
動かしてみると、なぜかfunctionがタイムアウトになり、途中で強制終了してしまいます。
なぜタイムアウト?と色々悩みましたが、結局のところpomに追加したライブラリが、ビルド済モジュールに含まれていなかったようです。ならClassNotFoundExceptionを出してくれよ…
ビルド済モジュールは、eclipse workspaceのtargetフォルダを見るか、実際にデプロイされたものを見たければAzure portalからKuduでダウンロードできます。
ビルドのしかたが悪かったという事で、下記を参考にpom.xmlを修正しました。
How to add dependency JAR in java azure functions
<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>
まだまだ色々試してみたいですが、とりあえずここまで。
#感想
社内ライブラリをAzure Functionsで使う事ができて満足。
でもやっぱ色々と準備するのが面倒ですね。
簡単なfunctionなら、やっぱjavascriptで作るのが一番簡単。
javaは今回のように社内の既存資産を使いまわしたり、ローカルでデバッグできるのがいいですね。
用途によって使い分けたいと思います。
#参考になったサイト
Java と Maven を使用して初めての関数を作成する (プレビュー)
javaでトリガーを設定する
Azure CLIインストール&ログイン(Windows版)
How to add dependency JAR in java azure functions