Azure Functions を Spring Cloud Functionでやってみた
Azure での Spring Cloud Function の概要 が例のごとくMavenだったのでGradle版でやります。
基本、ここのやっていることをそのままやる感じです。
環境
Windows 11
OpenJDK 11 (Microsoft Build)
VSCode
補足
2022/11時点
WSL on Ubuntu でやってましたが、動きませんでした!!!😿
Azure Functions Core Toolsが見つからないって言われました。Pathとかうまく見れないのかなぁ。C#だといけたんだけど。
(これで結構時間を溶かしました)
準備
Azure Functions のインスタンスを建てる
Java 11 で建てる。割愛。
Spring Initializr でプロジェクトの作成
選んだのは
- Spring Boot Dev Tools(いつものやつ)
- Lombok(いつものやつ)
plugins {
id 'org.springframework.boot' version '2.7.5'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('test') {
useJUnitPlatform()
}
:::
生成したプロジェクトをダウンロードして解凍して展開する。いつものフォルダ構成。
build.gradle を修正する
Azure Functions のライブラリは別途追加が必要なので追加する。
ついでにローカル起動&deploy用のpluginも追記する。
plugins {
id 'org.springframework.boot' version '2.7.5'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
id 'java'
+ id "com.microsoft.azure.azurefunctions" version "1.11.0"
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
+ ext {
+ set('springCloudAzureVersion', "4.4.1")
+ set('springCloudVersion', "2021.0.5")
+ }
dependencies {
+ implementation 'com.microsoft.azure.functions:azure-functions-java-library:2.1.0'
+ implementation 'org.springframework.cloud:spring-cloud-function-adapter-azure:3.2.7'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
+ jar {
+ enabled = true
+ archiveFileName = "${rootProject.name}-${version}.jar"
+ manifest {
+ attributes 'Main-Class' : 'com.example.demo.DemoApplication'
+ }
+ }
tasks.named('test') {
useJUnitPlatform()
}
+ azurefunctions {
+ subscription = 'サブスクリプションID'
+ resourceGroup = 'リソースグループ名'
+ appName = 'アプリ名'
+ pricingTier = 'Consumption' // AppServicePlanだとB2などプラン名を記述
+ region = 'japaneast'
+ runtime {
+ os = 'linux'
+ }
+ appSettings {
+ WEBSITE_RUN_FROM_PACKAGE = '1'
+ FUNCTIONS_EXTENSION_VERSION = '~4'
+ FUNCTIONS_WORKER_RUNTIME = 'java'
+ MAIN_CLASS = 'com.example.demo.DemoApplication'
+ }
+ auth {
+ type = 'azure_cli'
+ }
+ localDebug = "transport=dt_socket,server=y,suspend=n,address=5005"
+ }
Azure 構成ファイルを作成する
このへんは一緒のはず
Create Azure configuration files
※日本語サイトは更新がされていなかったので、英語サイトを参考にすること。
host.json のversionって別にFunctionsのバージョンってことではないのね。
Azure Functions 2.x 以降の host.json のリファレンス
host.json と local.settings.json を追加
{
"version": "2.0",
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[3.*, 4.0.0)"
},
"functionTimeout": "00:10:00"
}
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "",
"FUNCTIONS_WORKER_RUNTIME": "java",
"FUNCTIONS_EXTENSION_VERSION": "~4",
"MAIN_CLASS": "com.example.demo.DemoApplication",
"AzureWebJobsDashboard": ""
}
}
ソースコードの編集
ドメインオブジェクトを作成する
package com.example.demo.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
private String name;
}
package com.example.demo.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Greeting {
private String message;
}
コンポーネントの作成
Spring Boot アプリケーションを作成する
DemoApplication.java は Initializr で生成されたのをそのまま流用。
Hello.javaはそのまま。
package com.example.demo;
import java.util.function.Function;
import org.springframework.stereotype.Component;
import com.example.demo.model.Greeting;
import com.example.demo.model.User;
import reactor.core.publisher.Mono;
@Component
public class Hello implements Function<Mono<User>, Mono<Greeting>> {
@Override
public Mono<Greeting> apply(Mono<User> mono) {
return mono.map(user -> new Greeting("Hello, " + user.getName() + "!\n"));
}
}
関数の作成
ここはそのままで、エンドポイントが 『/hello』 の関数を作っている。
ControllerじゃなくてHandlerなんだな。
package com.example.demo;
import java.util.Optional;
import org.springframework.cloud.function.adapter.azure.FunctionInvoker;
import com.example.demo.model.Greeting;
import com.example.demo.model.User;
import com.microsoft.azure.functions.ExecutionContext;
import com.microsoft.azure.functions.HttpMethod;
import com.microsoft.azure.functions.HttpRequestMessage;
import com.microsoft.azure.functions.HttpResponseMessage;
import com.microsoft.azure.functions.HttpStatus;
import com.microsoft.azure.functions.annotation.AuthorizationLevel;
import com.microsoft.azure.functions.annotation.FunctionName;
import com.microsoft.azure.functions.annotation.HttpTrigger;
public class HelloHandler extends FunctionInvoker<User, Greeting> {
@FunctionName("hello")
public HttpResponseMessage execute(@HttpTrigger(name = "request", methods = { HttpMethod.GET,
HttpMethod.POST }, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<User>> request,
ExecutionContext context) {
User user = request.getBody()
.filter((u -> u.getName() != null))
.orElseGet(() -> new User(
request.getQueryParameters()
.getOrDefault("name", "world")));
context.getLogger().info("Greeting user name: " + user.getName());
return request
.createResponseBuilder(HttpStatus.OK)
.body(handleRequest(user, context))
.header("Content-Type", "application/json")
.build();
}
}
単体テストの作成
単体テストを追加する
お勧めされたので、検証のために単体テストを書きます。
assertjは使わない派なので、JUnitのみの記述に変更してます。
testメソッドはFunction関係ないテストってことなんだろうか。
package com.example.demo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.logging.Logger;
import org.junit.jupiter.api.Test;
import org.springframework.cloud.function.adapter.azure.FunctionInvoker;
import com.example.demo.model.Greeting;
import com.example.demo.model.User;
import com.microsoft.azure.functions.ExecutionContext;
import reactor.core.publisher.Mono;
class HelloTest {
@Test
void test() {
Mono<Greeting> result = new Hello().apply(Mono.just(new User("foo")));
assertEquals("Hello, foo!\n", result.block().getMessage());
}
@Test
void start() {
FunctionInvoker<User, Greeting> handler = new FunctionInvoker<>(
Hello.class);
Greeting result = handler.handleRequest(new User("foo"), new ExecutionContext() {
@Override
public Logger getLogger() {
return Logger.getLogger(HelloTest.class.getName());
}
@Override
public String getInvocationId() {
return "id1";
}
@Override
public String getFunctionName() {
return "hello";
}
});
handler.close();
assertEquals("Hello, foo!\n", result.getMessage());
}
}
ローカルでデバッグ
> .\gradlew azureFunctionsRun
表示されたURLをクリックして、メッセージが表示されていればOK
Functions にデプロイ
- (Option) サブスクリプションやテナントが複数ある場合は事前に設定しておく
- build.gradle に設定していても、AZ CLI のデフォルトサブスクリプションが違うだけで失敗するので注意
> az login --tenant 'テナントID'
> az account set --subscription 'サブスクリプション名 or サブスクリプションID'
- deploy task
> .\gradlew azurefunctionsDeploy
Deployment succeeded, but failed to list http trigger urls.
と言ってくるけど、https://functionsのドメイン/api/hello にアクセスすると、一応表示されます。
ちなみに、初回のデプロイはめちゃくちゃ遅くてタイムアウトとかするので、もう一回チャレンジすると成功するかも。
参考
- 公式Javaサンプル
-
Spring Cloud FunctionでAzure Functionsアプリ
- もたもたしてたら、もっとちゃんとやってらっしゃる方がいましたので、こちらのサイトのほうが親切です😓
とにかく情報が少ないのが辛いですね。。。がんばります。
Discussion