このエントリは2023/06/12現在の情報に基づいています。将来の機能追加・変更に伴い、記載内容からの乖離が発生する可能性があります。
純粋に個人の興味である。純正のFunctionsフレームワークでローカルでのテスト、クラウド上での動作はもちろん可能なのだが、他の開発フレームワークで作るとどうなるのか、というのを2023/06/12現在の情報でまとめた。調査条件・動作環境は以下の通り。
- Javaの開発フレームワーク
- Durable Functionはこの調査の対象外
- ローカル環境には、Function Core Toolsをインストール済み(Linux/Windows)
- HTTP trigger & Output bindingで動作確認済み
本家、というかAzure Functionsのフレームワーク
たぶん最初に触るやつ。
コマンド ラインから Azure に Java 関数を作成する / Create a Java function in Azure from the command line
https://learn.microsoft.com/azure/azure-functions/create-first-function-cli-java
関数部分だけを記述すればよいので、注力すべきところが明確でわかりやすい。とはいえ、他の開発フレームワークに慣れている場合、お作法の違いに戸惑いが発生するのも事実。以下はArcheTypeを使って作成したもの。
public class Function {
@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();
}
}
}
Spring Cloud
SpringはVMwareが中心となってオープンソースとして開発されている開発フレームワーク。
Spring by VMware Tanzu
https://spring.io/
Springには様々なプロジェクトがあり、その中に、クラウドベンダー非依存な形でFunctionアプリケーションを作成できるようにするためのSpring Cloud Functionというプロジェクトがある。
Spring Cloud Function
https://spring.io/projects/spring-cloud-function
まずはSpringのブログエントリを読んでから、AzureのSpring Cloud Functionを使ったチュートリアルをを見ると、基礎部分を把握した上で、わかりやすい書き方を理解できるので、学習しやすいかもしれない(日本語訳が気になる場合には、英語版を見たほうがいい)。
Spring Cloud Function in Azure
https://learn.microsoft.com/azure/developer/java/spring-framework/getting-started-with-spring-cloud-function-in-azure
Spring Cloud Function for Azure Function
https://spring.io/blog/2023/02/24/spring-cloud-function-for-azure-function
他のフレームワークと違う点は、Spring Cloud Functionがjava.util.function.Function/SupplierもしくはConsumer インターフェースを使って、入出力型を含む関数の構造を定義しているところ。そのため、
- functionインターフェースを作成
- functionをBeanとして登録
が必要。
依存関係
Azure Functionsに対応するためには、spring-cloud-function-adapter-azureを追加する。2023/06/12現在の最新は4.0.3。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-function-adapter-azure</artifactId>
<version>4.0.3</version>
</dependency>
あとazure-functions-maven-pluginは、Azure Functionsデプロイのためのプラグインで、デプロイに必要なApp Service PlanやSKU、リソースグループなどを指定できる。2023/06/12現在の最新は1.26.0。
<plugin>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-functions-maven-plugin</artifactId>
<version>1.26.0</version>
<configuration>
<appName>scf-samples</appName>
<resourceGroup>java-functions-group</resourceGroup>
<region>westus</region>
<appServicePlanName>java-functions-app-service-plan</appServicePlanName>
<pricingTier>EP1</pricingTier>
<hostJson>${project.basedir}/src/main/resources/host.json</hostJson>
<runtime>
<os>linux</os>
<javaVersion>17</javaVersion>
</runtime>
<funcPort>7071</funcPort>
<appSettings>
<property>
<name>FUNCTIONS_EXTENSION_VERSION</name>
<value>~4</value>
</property>
</appSettings>
</configuration>
<executions>
<execution>
<id>package-functions</id>
<goals>
<goal>package</goal>
</goals>
</execution>
</executions>
</plugin>
Azure Functions
https://github.com/microsoft/azure-maven-plugins/wiki/Azure-Functions
実際のコードはこんな感じ。少々違いはあるが、おおよその構造は純正のFunctionフレームワークとあまり変わらない。
@Component
public class Function {
@Autowired
private GreetingService service;
@FunctionName("greeting")
public HttpResponseMessage execute(
@HttpTrigger(
name="req",
methods = {HttpMethod.GET},
authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<User>> request,
ExecutionContext context) {
context.getLogger().warning("Using Java (" + System.getProperty("java.version") + ")");
User user = request.getBody()
.filter(u->u.getName()!=null)
.orElseGet(()->new User(request.getQueryParameters().getOrDefault("name","NO NAME")));
context.getLogger().warning("User: " + user.getName());
return request.createResponseBuilder(HttpStatus.OK)
.body(service.apply(user))
.header("Content-Type", "application/json")
.build();
}
}
Quarkus
QuarkusはRed Hatが中心となってオープンソースとして開発されている開発フレームワーク。2023/06/12現在3.1.2が最新。
Quarkus – Supersonic Subatomic Java
https://quarkus.io/
Azureのドキュメントでは、FunqyベースでAzure Function Appを作成するクイックスタートがある。
Azure Functions 上で Quarkus を使用してサーバーレス Java アプリをデプロイする / Deploy serverless Java apps with Quarkus on Azure Functions
https://learn.microsoft.com/azure/azure-functions/functions-create-first-quarkus
だが、Quarkus 3でAzure Functionsとネイティブ統合されたため、Funqyを意識せずにFunction Appを作成できるようになった。以下の動画で、Java ChampionのDaniel Ohが説明している。
コードの雛型は、GradleやMavenでArcheTypeを使って作成することも、以下の方式で作成することもできる。
quarkusCLIの利用
https://quarkus.io/guides/cli-tooling- IntelliJ IDEAなどのIDE
- Quarkus – Start coding with code.quarkus.ioの利用
https://code.quarkus.io
コードを見ると、以下の通り。@InjectされているGreetingServiceがあったりと少々違いはあるが、こちらも本家のコードとほとんど変わりがない。
public class Function {
@Inject
GreetingService service;
@FunctionName("greeting")
public HttpResponseMessage run(
@HttpTrigger(
name = "req",
methods = {HttpMethod.GET},
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(service.greeting(name)).build();
}
}
}
Micronaut
Micronaut Foundationが中心になって、オープンソースとして開発しているフレームワークで、リフレクションなどの動的な部分をなくすことを目指している。2023/06/12現在3.9.3が最新 (現在4.x系を開発中)。
Micronaut
https://micronaut.io/
Azureのドキュメントには記載がないので、MicronautのAzure Functionに関するドキュメントを見るしかない。以下のドキュメントの通り、Micronautでは、
micronaut-azure-functionを使うSimple Azure Functionsmicronaut-azure-function-httpを使うAzure HTTP Functions
の2種類の方法で対応している。前者はDependency Injectionを利用可能な、より低レベルの仕組み。後者は通常のMicronaut コントローラを記述し、Azure Functionで動作可能にする仕組み。
Azure Function Support
https://micronaut-projects.github.io/micronaut-azure/latest/guide/#azureFunction
Simple Azure Functions
以下はSimple Azure Functionsの例。Spring Cloudの記述を少々手直ししただけ、とも読める(例えば@Autowiredを@Injectに変更する、など)。
public class Function extends AzureFunction {
@Inject
GreetingService greetingService;
@FunctionName("greeting")
public HttpResponseMessage invoke(
@HttpTrigger(
name = "req",
methods = {HttpMethod.GET, HttpMethod.POST},
authLevel = AuthorizationLevel.ANONYMOUS)
HttpRequestMessage<Optional<User>> request,
final ExecutionContext context) {
User user = request.getBody()
.filter(u->u.getName()!=null)
.orElseGet(()->new User(request.getQueryParameters().getOrDefault("name","NO NAME")));
context.getLogger().warning("User: " + user.getName());
return request.createResponseBuilder(HttpStatus.OK)
.body(greetingService.apply(user))
.header("Content-Type", "application/json")
.build();
}
}
Azure HTTP Functions
Azure HTTP Functionsの場合、Function定義の部分はSimple Azure Functionsと同じだが、コントローラを作成する点で、Micronaut使いにとってはわかりやすい(はず)。大きな違いは以下だが、サンプルコードを見ると一目瞭然。
@FunctionNameで指定するのは文字通り関数名であり、コンテキストパスには無関係(Simple Azure Functionsや純正のFunctionsフレームワークだと、/api/{FunctionName}とコンテキストパスに現れる)- Azure HTTP Functionsの場合、
io.micronaut.azure.function.http.AzureHttpFunctionを継承する(Simple Azure Functionの場合はio.micronaut.azure.function.AzureFunctionを継承) super.route(request, context)を返す
public class Function extends AzureHttpFunction {
@FunctionName("greeting")
public HttpResponseMessage hello(
@HttpTrigger(
name = "req",
methods = {HttpMethod.GET,HttpMethod.POST},
route = "{*route}",
authLevel = AuthorizationLevel.ANONYMOUS)
HttpRequestMessage<Optional<String>> request,
ExecutionContext context) {
if (context != null) {
context.getLogger().info("Executing Function: " + getClass().getName());
}
return super.route(request, context);
}
}
で、GET、POSTのハンドラは以下。このあたりはMicronautのお作法そのまま。
@Controller("/greeting")
public class GreetingController {
final String format = "Hello, %s!";
@Post( uri = "/",
consumes = MediaType.APPLICATION_JSON,
produces = MediaType.APPLICATION_JSON)
public Greeting postHello(@Body Optional<User> user) {
return new Greeting(String.format(format, user.orElse(new User("NO NAME")).getName()));
}
@Get(uri = "{?name}",
produces = MediaType.APPLICATION_JSON)
public Greeting getHello(Optional<String> name) {
return new Greeting(String.format(format, name.orElse("NO NAME")));
}
}
ここらあたりで
おそらく他のフレームワークでも対応しているものがあるはずだが、いったんここまでにしておく。