演習 - SignalR Service を使用して Web アプリケーションの自動更新を有効にする

完了

新しい機能をサポートするには、いくつかの新しい関数を作成し、クライアント上で JavaScript コードを更新する必要があります。

SignalR アカウントを作成する

サンドボックス サブスクリプションに SignalR アカウントを追加する必要があります。

  1. 最初の手順は、Cloud Shell 内で次のコマンドを実行し、サンドボックス リソース グループ内に新しい SignalR アカウントを作成することです。 このコマンドが完了するまで数分かかる場合があるため、完了するまで待ってから次の手順に進んでください。

    SIGNALR_SERVICE_NAME=msl-sigr-signalr$(openssl rand -hex 5)
    az signalr create \
      --name $SIGNALR_SERVICE_NAME \
      --resource-group <rgn>[sandbox resource group name]</rgn> \
      --sku Free_DS2 \
      --unit-count 1
    
  2. SignalR サービスが Azure Functions で正常に動作するには、サービス モードをサーバーレスに設定する必要があります。 次のコマンドを使ってサービス モードを構成します。

    az resource update \
      --resource-type Microsoft.SignalRService/SignalR \
      --name $SIGNALR_SERVICE_NAME \
      --resource-group <rgn>[sandbox resource group name]</rgn> \
      --set properties.features[flag=ServiceMode].value=Serverless
    

ローカル設定を更新する

アプリを実行するには、ローカル設定に保存された SignalR 接続文字列を追加する必要があります。

  1. Cloud Shell で次のコマンドを実行し、この演習で作成したリソース用の接続文字列を取得します。

    SIGNALR_CONNECTION_STRING=$(az signalr key list \
      --name $(az signalr list \
        --resource-group <rgn>[sandbox resource group name]</rgn> \
        --query [0].name -o tsv) \
      --resource-group <rgn>[sandbox resource group name]</rgn> \
      --query primaryConnectionString -o tsv)
    
    printf "\n\nReplace <SIGNALR_CONNECTION_STRING> with:\n$SIGNALR_CONNECTION_STRING\n\n"
    
  2. アプリケーションをクローンした場所に移動し、Visual Studio Code で start フォルダーを開きます。 エディターで local.settings.json を開き、ファイルを更新できるようにします。

  3. local.settings.json で、AzureSignalRConnectionString 変数を、Cloud Shell にリストされている値で更新し、ファイルを保存します。

クライアント接続を管理する

Web クライアントでは SignalR クライアント SDK を使用して、サーバーへの接続を確立します。 SDK では、(慣例に従って) negotiate という名前の関数を介して接続を取得し、サービスに接続します。

  1. F1 キーを押して、Visual Studio Code のコマンド パレットを開きます。

  2. [Azure Functions: 関数の作成] コマンドを検索して選択します。

  3. 入力を求められたら、次の情報を指定します。

    名前
    テンプレート HTTP トリガー
    名前 negotiate
    承認レベル 匿名

    Visual Studio Code でエクスプローラー ウィンドウを更新し、更新内容を確認します。 これで、negotiate という名前のフォルダーが関数アプリで使用できるようになりました。

  4. negotiate/function.json を開き、次の SignalR バインディング定義を bindings 配列に追加します。

    {
        "type": "signalRConnectionInfo",
        "name": "connectionInfo",
        "hubName": "stocks",
        "direction": "in",
        "connectionStringSetting": "AzureSignalRConnectionString"
    }
    

    この構成では、接続されているクライアントを識別するために使用される、接続情報を関数でサーバーに返すことができます。

  5. 次に、negotiate/index.js を開き、既存の関数コードを以下のコードに置き換えます。

    module.exports = async function (context, req, connectionInfo) {
        context.res.body = connectionInfo;
    };
    

    関数が呼び出されると、SignalR 接続が関数への応答として返されます。

これで SignalR 接続情報を返す関数が実装されたので、クライアントへの変更のプッシュを担当する関数を作成できます。

データベースの変更を検出してブロードキャストする

最初に、データベースの変更をリッスンする新しい関数を作成する必要があります。 この関数では、データベースの変更フィードに接続する Azure Cosmos DB トリガーを使用します。

  1. F1 キーを押して、Visual Studio Code のコマンド パレットを開きます。

  2. [Azure Functions: 関数の作成] コマンドを検索して選択します。

  3. 入力を求められたら、次の情報を指定します。

    名前
    テンプレート Azure Cosmos DB のトリガー
    名前 stocksChanged
    Azure Cosmos DB アカウントのアプリ設定 AzureCosmosDBConnectionString
    データベース名 stocksdb
    コレクション名 stocks
    リースのコレクション名 leases
    Create lease collection if not exists(リース コレクションが存在しない場合は作成する) true

    これで、stocksChanged という名前のフォルダーが作成されました。これには新しい関数のファイルが含まれます。

  4. Visual Studio Code で stocksChanged/function.json を開きます。

  5. "feedPollDelay": 500 プロパティを既存のトリガー バインディング定義に追加します。 この設定により、データベースの変更を確認するまでの待機時間が Azure Cosmos DB に示されます。 構築するアプリケーションは、プッシュベースのアーキテクチャを基に構築されます。 しかし、バックグラウンドでは、変更を検出するために Azure Cosmos DB で継続的に変更フィードが監視されます。 feedPollDelay は、Web アプリケーションでデータの変更を公開する方法ではなく、Azure Cosmos DB の内部で変更を認識する方法を指します。

    これで関数の Azure Cosmos DB バインディングは、次のコードのようになるはずです。

    {
      "type": "cosmosDBTrigger",
      "name": "documents",
      "direction": "in",
      "leaseCollectionName": "leases",
      "connectionStringSetting": "AzureCosmosDBConnectionString",
      "databaseName": "stocksdb",
      "collectionName": "stocks",
      "createLeaseCollectionIfNotExists": "true",
      "feedPollDelay": 500
    }
    
  6. 次に、以下の SignalR 出力バインディング定義を、bindings 配列に追加します。

    {
      "type": "signalR",
      "name": "signalRMessages",
      "connectionString": "AzureSignalRConnectionString",
      "hubName": "stocks",
      "direction": "out"
    }
    

    このバインディングにより、関数でクライアントに変更をブロードキャストできるようになります。

  7. stocksChanged/index.js ファイルを更新し、以下のコードを反映させます。 すべての構成の長所は、関数コードがシンプルであることです。

    module.exports = async function (context, documents) {
        const updates = documents.map(stock => ({
            target: 'updated',
            arguments: [stock]
        }));
    
        context.bindings.signalRMessages = updates;
        context.done();
    }
    

    変更の配列は、SignalR が読み取る形式のオブジェクトを作成すると準備されます。 更新されたすべての株式は、updated に設定された target プロパティと共に、arguments 配列に提供されます。

    target プロパティの値は、SignalR による特定のメッセージ ブロードキャストがリッスンされるときに、クライアントで使用されます。

Web アプリケーションを更新する

public/index.html を開き、ID が app の現在の DIV の代わりに以下のコードを貼り付けます。

<div id="app" class="container">
    <h1 class="title">Stocks</h1>
    <div id="stocks">
        <div v-for="stock in stocks" class="stock">
            <transition name="fade" mode="out-in">
                <div class="list-item" :key="stock.price">
                    <div class="lead">{{ stock.symbol }}: ${{ stock.price }}</div>
                    <div class="change">Change:
                        <span
                            :class="{ 'is-up': stock.changeDirection === '+', 'is-down': stock.changeDirection === '-' }">
                            {{ stock.changeDirection }}{{ stock.change }}
                        </span>
                    </div>
                </div>
            </transition>
        </div>
    </div>
</div>

このマークアップでは transition 要素が追加されます。これにより、Vue.js では、株式データが変更されたときに繊細なアニメーションを実行できるようになります。 株式が更新されたときに、タイルがフェードアウトし、すぐにまた表示されます。 このようにして、ページが株式データでいっぱいになった場合、ユーザーは変更された株式を簡単に確認することができます。

次に、index.html.js への参照のすぐ上に以下のスクリプト ブロックを追加します。

<script src="https://cdn.jsdelivr.net/npm/@aspnet/signalr@1.1.0/dist/browser/signalr.js"></script>

このスクリプトでは、SignalR SDK への参照が追加されます。

ここで public/index.html.js を開き、ファイルを以下のコードに置き換えます。

const LOCAL_BASE_URL = 'http://localhost:7071';
const REMOTE_BASE_URL = '<FUNCTION_APP_ENDPOINT>';

const getAPIBaseUrl = () => {
    const isLocal = /localhost/.test(window.location.href);
    return isLocal ? LOCAL_BASE_URL : REMOTE_BASE_URL;
}

const app = new Vue({
    el: '#app',
    data() {
        return {
            stocks: []
        }
    },
    methods: {
        async getStocks() {
            try {
                const apiUrl = `${getAPIBaseUrl()}/api/getStocks`;
                const response = await axios.get(apiUrl);
                app.stocks = response.data;
            } catch (ex) {
                console.error(ex);
            }
        }
    },
    created() {
        this.getStocks();
    }
});

const connect = () => {
    const connection = new signalR.HubConnectionBuilder()
                            .withUrl(`${getAPIBaseUrl()}/api`)
                            .build();

    connection.onclose(()  => {
        console.log('SignalR connection disconnected');
        setTimeout(() => connect(), 2000);
    });

    connection.on('updated', updatedStock => {
        const index = app.stocks.findIndex(s => s.id === updatedStock.id);
        app.stocks.splice(index, 1, updatedStock);
    });

    connection.start().then(() => {
        console.log("SignalR connection established");
    });
};

connect();

先ほど行った変更で 2 つの目標が達成されました。つまり、クライアントからポーリング ロジックがすべて削除され、サーバーからのメッセージをリッスンするハンドラーが追加されました。

新しいヘルパー関数が導入され、アプリケーションをローカルおよびデプロイされたコンテキストで簡単に動作させることができます。

const LOCAL_BASE_URL = 'http://localhost:7071';
const REMOTE_BASE_URL = '<FUNCTION_APP_ENDPOINT>';

const getAPIBaseUrl = () => {
    const isLocal = /localhost/.test(window.location.href);
    return isLocal ? LOCAL_BASE_URL : REMOTE_BASE_URL;
}

getAPIBaseUrl 関数では、アプリがローカルで実行されているか、Azure にデプロイされているかに応じて、適切な URL が返されます。 今後の演習では、このアプリケーションをクラウドにデプロイするときに、ストレージ アカウント エンドポイントによってプレースホルダー <REMOTE_BASE_URL> が置き換えられます。

これで、クライアントに変更がプッシュされる、Vue.js に関連するコードが簡素化されました。 スクリプト ファイルに貼り付けたコードのこのセグメントについて考えてみます。

const app = new Vue({
    el: '#app',
    data() {
        return {
            stocks: []
        }
    },
    methods: {
        async getStocks() {
            try {
                const apiUrl = `${getAPIBaseUrl()}/api/getStocks`;
                const response = await axios.get(apiUrl);
                app.stocks = response.data;
            } catch (ex) {
                console.error(ex);
            }
        }
    },
    created() {
        this.getStocks();
    },
});

ここでは、前の実装と同じ株式配列が使用されますが、ポーリング コードはすべて削除され、getStocks のロジックはそのままです。 getStocks 関数は引き続き、コンポーネントの作成時に呼び出されます。

次に、クライアント コードのこのセグメントについて考えてみます。

const connect = () => {
    const connection = new signalR.HubConnectionBuilder()
                            .withUrl(`${getAPIBaseUrl()}/api`)
                            .build();

    connection.onclose(()  => {
        console.log('SignalR connection disconnected');
        setTimeout(() => connect(), 2000);
    });

    connection.on('updated', updatedStock => {
        const index = app.stocks.findIndex(s => s.id === updatedStock.id);
        app.stocks.splice(index, 1, updatedStock);
    });

    connection.start().then(() => {
        console.log("SignalR connection established");
    });
};

connect();

ページが読み込まれたときに、connect 関数が呼び出されます。 connect 関数の本体での最初のアクションは、SignalR SDK を使用し、HubConnectionBuilder を呼び出して接続を作成することです。 その結果は、サーバーへの SignalR 接続になります。

サーバーがタイムアウトになった後、適切に回復させるために、connect を再度呼び出して接続が閉じられた 2 秒後に、onclose ハンドラーによって接続が再確立されます。

クライアントでは、サーバーからのメッセージを受信したときに、on('updated',... 構文を通じてメッセージをリッスンします。 更新が受信されると、以下のアクションが行われます。

  • 変更された株式が配列に配置される。
  • 古いバージョンが削除される。
  • 新しいバージョンが、配列内の同じインデックスの位置に挿入される。

このように配列を操作することで、Vue ではデータの変更を検出し、アニメーション効果をトリガーしてユーザーに変更を通知することができます。

アプリケーションを実行する

これで、ローカルで実行されているアプリの新しいバージョンを確認できます。

F5 キーを押して、関数アプリのデバッグを開始します。

次に、Visual Studio Code で新しいターミナル ウィンドウを開き、npm start を実行します。

npm start

スクリプトによって自動的にブラウザーが開かれ、http://localhost:8080. に移動されます ブラウザーを自動的に開けない場合は、手動で http://localhost:8080 に移動できます。

自動更新を確認する

これで、アプリケーションのデータを変更し、UI で自動的に更新する方法を確認できます。

  1. 画面の一方の側に Visual Studio Code を配置し、もう一方の側に Web ブラウザーを配置します。 このように、データベースに変更が加えられたときに、UI の更新を確認できます。

  2. Visual Studio Code に戻り、新しい統合ターミナルで次のコマンドを入力します。 ここでも、アプリケーションで株式 ABC が自動的に更新されることを確認します。

    npm run update-data
    

データベースが更新された後、UI は次のスクリーンショットのようになります。

End state of serverless web app.

完了したら、実行中のプロセスを停止します。

  • Web サーバーを停止するには、Web サーバーを実行しているターミナル ウィンドウで [kill process](プロセスの強制終了) (ごみ箱アイコン) を選択します。

  • 関数アプリを停止するには、[停止] ボタンを選択するか、Shift + F5 キーを押してください。