前回はIntelliJ IDEAを利用して、JavaでAzure Functionsの関数アプリを作成する方法を紹介した。今回は、作成した関数アプリをローカルで実行する方法や、関数アプリに対して他のAzureサービスからデータを入力、出力する方法について取り上げる。

Azure Functions Code Toolsのインストール

前回の記事では作成した関数アプリをAzure Functionsに直接デプロイして動作を確認したが、実際の開発ではデプロイする前にローカルで挙動を確認してデバッグを行いたい。そこでMicrosoftでは、ローカルで関数アプリを実行するための「Azure Functions Core Tools」というツールを提供している。Azure Functions Core ToolsにはAzure Functionsのランタイムのバージョンに応じて1.x / 2.x / 3.xの3つのバージョンがあり、v1.xはWindows版のみ、v2.xとv3.xはWindows版、macOS版、Linux版が用意されている。本稿ではv3.xを使用する。

Azure Functions Core Toolsのインストール方法は、以下のページに詳しく解説されている。

Windowsの場合、インストーラをダウンロードして、画面の指示にしたがってインストールすればよい。

  • WindowsへのAzure Functions Core Toolsのインストール

    WindowsへのAzure Functions Core Toolsのインストール

インストールが完了したら、インストールした場所のパスをPATH環境変数に追加しておこう。64bit版であれば、デフォルトのインストールパスは「C:\Program Files\Microsoft\Azure Functions Core Tools」になる。

  • PATH環境変数にAzure Functions Core Toolsのインストールパスを設定する

    PATH環境変数にAzure Functions Core Toolsのインストールパスを設定する

macOS版は、Homebrewを利用して次のコマンドでインストールできる。

> brew tap azure/functions
> brew install azure-functions-core-tools@3

インストールが完了したら、次のように「func」コマンドがインストールされたディレクトリを探して、そのパスを環境変数に追加する。

$ which func
/usr/local/Cellar/azure-functions-core-tools@3/3.0.2881/func
$ export PATH=$PATH:/usr/local/Cellar/azure-functions-core-tools@3/3.0.2881

Windows版/macOS版ともに、ターミナルで次のようにfuncコマンドが実行できたらインストール成功だ。

  • funcコマンドの実行

    funcコマンドの実行

Azure Functions Core Toolsを使えば、ローカルのコマンドプロンプトやターミナルで関数アプリのプロジェクトを作成したり、テンプレートを使用して各種言語で関数を作成し、実行してテストしたりすることができる。本稿の例では、関数アプリの開発にはIntelliJ IDEAを利用しているが、もちろんIntelliJ IDEAからでもAzure Functions Core Toolsを使ったローカルでの関数の実行ができる。

関数アプリをローカルで実行する

Azure Functions Core Toolsのインストールができたら、IntelliJ IDEAで先週作成したプロジェクトを開いて、これをローカルで実行してみよう。注意点として、Azure Functions Core Toolsは本稿執筆時点ではJava 9以降に対応していないため、Azure Functions Core Toolsのインストールができたら、IntelliJ IDEAで前回に作成したプロジェクトを開いて、これをローカルで実行してみよう。

HttpTriggerFunction.javaを開くと、run()メソッドの行に、下図のように緑色のマークが表示されている。

  • ソースコードエディタから関数のローカルでの実行ができる

    ソースコードエディタから関数のローカルでの実行ができる

これを右クリックすると、次のようなプルダウンメニューが表示される。このメニューから、関数のローカルでの実行やデバッグを行うことができる。

  • 関数のローカル実行の設定

    関数のローカル実行の設定

その前に、初回の実行ではfuncコマンドのパスなどの設定が必要だ。一番下の[Edit 'Run Functions - azur...']を選択して設定ダイアログを表示させよう。ここでは、次のように「Function CLI」の項目にfuncコマンドのパスを設定する。また、環境変数JAVA_HOMEの値としてJDKのインストールパスを設定しておく。

  • funcコマンドのパスとJAVA_HOME環境変数を設定する

    funcコマンドのパスとJAVA_HOME環境変数を設定する

設定できたら、今度はプロダウンメニューの一番上の[Run 'Run Functions - azur...']を選択すれば、関数がローカルで実行される。IDEAのRunウィンドウにローカルのエンドポイントが出力されるので、ここにWebブラウザからアクセスすれば関数の挙動を確かめることができる。

  • ローカルでの関数の実行

    ローカルでの関数の実行

関数アプリからCosmosDBへのデータの書き込み

さて、話をJavaを使った関数アプリの作成に戻そう。

第14回の記事でも少し触れたように、Azure Functionsでは関数がConmosDBなどのバックエンドのサービスからデータを受け取ったり、逆にデータを渡したりすることができる。この機能は「バインド」と呼ばれていて、データを受け取る「入力バインド」と、データを渡す「出力バインド」がある。入力バインドから受け渡されるデータは、パラメータとして関数に提供される。出力バインドに受け渡す場合は、関数の戻り値として渡す方法や、Bindigオブジェクト経由で渡す方法がある。

そこで次は、CosmosDBとバインドしてデータの保存や取り出しを行う関数を作成してみよう。まず準備として、第4回の記事を参考にしてデータを保存するCosmosDBのストレージアカウントを作成しておく。本稿の例では、「mynavistorage」という名前のアカウントを用意した。

  • CosmosDBのストレージアカウントを用意する

    CosmosDBのストレージアカウントを用意する

最初に、HTTPで受け取ったパラメータをCosmosDBに書き込む関数を作ってみる。IntelliJ IDEAで新しい関数クラスを作成するには、プロジェクトツリーから対象のパッケージ名を右クリックして「New」-「Azure Function Class」と選択すればよい。

  • 新しい関数クラスの作成

    新しい関数クラスの作成

関数名(Function Name)を入力して[OK]を押せば、選んだトリガーの関数の雛形が作成される。今回はHTTPトリガーを洗濯した。

  • 関数名とトリガーの種類を指定する

    関数名とトリガーの種類を指定する

プログラムは次のようにした。

public class AddMessageItem {
    @FunctionName("addMessageItem")
    public HttpResponseMessage run(
            // HTTPトリガーの設定
            @HttpTrigger(name = "req",
                    methods = {HttpMethod.GET, HttpMethod.POST},
                    authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
            // CosmosDBへの出力バインドの設定
            @CosmosDBOutput(name = "database",
                    databaseName = "MessageList",
                    collectionName = "Messages",
                    connectionStringSetting = "AzureCosmosDBConnection",
                    createIfNotExists = true) OutputBinding<String> outputItem,
            final ExecutionContext context
    ) {
        context.getLogger().info("Java HTTP trigger processed a request.");

        // ランダムのIDを生成
        final int id = Math.abs(new Random().nextInt());

        // リクエストパラメータの取り出し
        String user = request.getQueryParameters().get("user");
        String message = request.getQueryParameters().get("msg");

        // JSON作成
        final String jsonDocument =  "{ \"id\": \"" + id + "\", \"message\": \"" + message + "\", \"user\": \"" + user + "\" }";
        // 出力バインドへのJSONの書き出し
        outputItem.setValue(jsonDocument);

        // HTTPレスポンス
        return request.createResponseBuilder(HttpStatus.OK)
                .body("Document created successfully.")
                .build();
    }
}

@FunctionNameで関数名を指定する部分や、@HttpTriggerでHTTPトリガーの設定を行う部分は、前回と同様である。CosmosDBの出力バインドは、@CosmosDBOutputアノテーションを使用して設定できる。run()メソッドの第2引数に対して@CosmosDBOutputアノテーションを指定し、パラメータでバインディングの設定を記述している。出力バインドのオブジェクト本体はOutputBinding<String>である。

この例では、@CosmosDBOutputに対して次のようなパラメータを設定している。

パラメータ名 内容
name 任意の名前
databaseName CosmosDBのデータベース名
collectionName CosmosDBのコレクション名(コンテナ名)
connectionStringSetting 接続設定のプロパティ名
createIfNotExists コレクションが存在しない場合、新規で作成するか否か(true/false)

これらのうち、createIfNotExists以外はすべて必須で、それぞれCosmosDBのデータベース情報を記述する。connectionStringSettingについては後述する。

このプログラムの場合、HTTPリクエストで「user」と「msg」という2つの値を受け取り、ランダムに生成したidを付け加えた3つの値をJSONデータとしてCosmosDBに登録する。出力バインドに対するデータの書き込みはOutputBinding<String>のsetValue()メソッドで行う。今回は出力バインドがCosmosDBに紐づいているため、CosmosDBへの書き込みになる。

さて、アプリからCosmosDBに接続するには、データベース名などの情報に加えて接続キーの設定が必要となる。ローカルで実行する場合は、先ほどの「Edit 'Run functions - azur...'」の設定項目で、App Settingsの部分に接続キーの設定を追加する。接続キーの情報は、AzureポータルのCosmosDBアカウントのメニューから「キー」を選択すれば確認できる。今回はプライマリ接続文字列を使用する。

  • CosmosDBの接続キーの確認

    CosmosDBの接続キーの確認

次の例は、「AzureCosmosDBConnection」というプロパティ名で接続キーの文字列をセットしたものだ。プロパティ名は任意の文字列でよい。このプロパティ名を、先ほどの@CosmosDBOutputのconnectionStringSettingパラメータの値としてセットするわけだ。

  • 任意のプロパティ名で接続文字列を追加する

    任意のプロパティ名で接続文字列を追加する

以上、準備ができたらAddMessageItemをローカルで実行し、エンドポイントのURLに対してuserとmsgのパラメータを付けてWebブラウザからアクセスしてみよう。本稿の例の場合、「http://localhost:7071/api/addMessageItem?user=mynavi&msg=Hello」のようなリクエストになる。

データの登録に成功すると、Webブラウザには「Document created successfully.」のように表示されるはずだ。この時点でAzureポータルでCosmosDBの対象のデータベースを見てみると、次のように送ったデータが登録されていることを確認できる。

  • CosmosDBにデータが書き込まれた

    CosmosDBにデータが書き込まれた

CosmosDBから関数アプリへのデータの読み込み

続いて、CosmosDBを入力バインドにしてデータを読み込む関数について見てみよう。次のコードは、先ほどの例と同じデータベースに対して、今度は入力バインドを指定してデータを読み込む例である。HTTPのパラメータとしてIDを受けとり、そのIDに一致するデータをHTTPレスポンスとして返す。関数名は「getMessageItem」とした。

package org.example.functions;

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

/**
 * Azure Functions with HTTP Trigger.
 */
public class GetMessageItem {
    @FunctionName("getMessageItem")
    public HttpResponseMessage run(
            // HTTPトリガーの設定
            @HttpTrigger(name = "req",
                    methods = {HttpMethod.GET, HttpMethod.POST},
                    authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
            // CosmosDBからの入力バインドの設定
            @CosmosDBInput(name = "database",
                    databaseName = "MessageList",
                    collectionName = "Messages",
                    id = "{Query.id}",
                    connectionStringSetting = "AzureCosmosDBConnection") Optional<String> item,
            final ExecutionContext context) {
        context.getLogger().info("Java HTTP trigger processed a request.");

        // データの取得
        String result = item.orElse("Not found.");

        // HTTPレスポンス
        return request.createResponseBuilder(HttpStatus.OK).body(result).build();
    }
}

CosmosDBを入力バインドに設定するには@CosmosDBInputアノテーションを使用する。この例では、@CosmosDBInputに次のようなパラメータを指定している。

パラメータ名 内容
name 任意の名前
databaseName CosmosDBのデータベース名
collectionName CosmosDBのコレクション名(コンテナ名)
id 検索対象のID(リクエストパラメータから取得)
connectionStringSetting 接続設定のプロパティ名

name、databaseName、collectionName、connectionStringSettingについては@CosmosDBOutputの時と同様である。idは読み込みたいデータのidを指定するもので、この例ではリクエストパラメータとして取得したいので、「{Query.id}」と記述している。{Query.id}は、トリガに渡されたクエリのidパラメータの値を表す。

この例では、入力バインドはCosmodDB内の対象idのデータに直結し、そのデータがOptional<String>オブジェクトとして渡される。したがって、このオブジェクトから中身を取り出して返せばよい。

この関数をローカルで実行して、取得したいデータのidをパラメータとしてWebブラウザからアクセスしてみよう。本稿の例の場合、「http://localhost:7071/api/getMessageItem?id=1658966648」のようなリクエストになった。データの取得に成功すれば、Webブラウザには次のようなJSONデータが返ってくる。

  • CosmosDBから取得したJSONデータが表示される

    CosmosDBから取得したJSONデータが表示される

Azure Functionsへのデプロイ

ローカルでの動作確認ができたので、最後に実際にAzure Functionsにデプロイしてみよう。前回の例と同様に、プロジェクト名を右クリックして[Azure]-[Deploy to Azure Functions]を選択する。ただし今回は、CosmosDBを使用するので、次のダイアログのApp Settingsのところで、ローカルの場合と同様にCosmosDBの接続キーの情報を設定する必要がある。

  • ローカルの場合と同様にCosmosDBへの接続文字列を追加する

    ローカルの場合と同様にCosmosDBへの接続文字列を追加する

これで[Run]をクリックすれば、作成したaddMessageItemとgetMessageItemの2つの関数がAzure Functionsにデプロイされて、Azureプラットフォーム上でリクエストを受け付けられるようになる。

今回は入力バインド、出力バインドをそれぞれ異なる関数で使用したが、ひとつの関数で両者を組み合わせて使用することや、複数の入力/出力バインドを同時に設定することもできる。Azure Functionsは入力、出力ともに多彩なバインドをサポートしているため、組み合わせることでビジネス上のさまざまなシナリオに対応できるだろう。