はじめに

今回は前回前々回に引き続きAzure SignalR Serviceの紹介をします。前回作成した「現在の株式情報の一覧を表示するアプリケーション」を完成させていきながら、Azure SignalR Serviceを使ったアプリケーションについて説明していきます。

今回作成するアプリケーション

前回までにアプリケーションのベースとなる部分を作成したので、今回はAzure Cosmos DBとAzure SignalR Serviceを連携して情報の更新をリアルタイムで画面上で確認できるようにする実装を行い、アプリケーションを完成させていきます。 なお今回実装するソースコードについては、こちらからダウンロードができます。

更新があった株式情報の通知を行うFunctionsの実装

まずは前回に引き続き、Functionsの実装を行っていきます。今回作成する関数はnegotiate、stocksChangedの2種類のFunctionsです。なお、negotiate関数については前々回に紹介したものと同じ実装となるので説明は省略します。negotiate関数は前々回に説明した通り、クライアントとSignalR Serviceインスタンスが接続を開始する際に呼び出される定型的な関数です。

以下は、株式情報に更新があった際に実行されるstockChanged関数を登録するためのコードです。

株式情報更新通知の関数定義(stockChanged/functions.json)

{
  "disabled": false,
  "bindings": [
    {
      "type": "cosmosDBTrigger", ・・・①
      "name": "documents",
      "direction": "in",
      "leaseCollectionName": "leases",
      "connectionStringSetting": "AzureCosmosDBConnectionString",
      "databaseName": "stocksdb",
      "collectionName": "stocks",
      "createLeaseCollectionIfNotExists": "true",
      "feedPollDelay": 500
    },
    {
      "type": "signalR", ・・・②
      "name": "signalRMessages",
      "connectionString": "AzureSignalRConnectionString",
      "hubName": "stocks",
      "direction": "out"
    }
  ]
}

ここでは、関数の起動トリガーが「cosmosDBTrigger」で、出力先が「signalR」となっています(②)。まず起動トリガーに設定されている「cosmosDBTrigger」ですが、これはフィードをAzure Functionsがトリガーとして利用できるようにしたものです。

フィードとは、指定したCosmos DBのコンテナ内のデータに変更があったことを通知するためのしくみのこと。これによって株価データの更新をトリガーとしてこの関数を起動することができます。さらに出力先が「signalR」であることで、更新内容をSignalR Service経由でクライアントにリアルタイムに送信することができるようになります。

続いて、stockChanged関数の本体です。

株式情報更新通知関数(stockChanged/index.js)

module.exports = async function (context, documents) {
  // 更新内容をSignalRに出力できる形式に変換 ・・・①
  const updates = documents.map(stock => ({
      target: 'updated',
      arguments: [stock]
  }));

  // 更新内容をSignalRに出力 ・・・②
  context.bindings.signalRMessages = updates;
  context.done();
}

関数本体の実装では、Cosmos DBより通知された更新内容(関数の引数にあるdocuments)をもとにSignalRに出力できる形式に変換して(①)、出力をします(②)。形式の変換時に「target: 'updated'」と指定することで、クライアント側で同名のSignalR用のイベントリスナーが呼びされるようになります。

画面の実装

次は現在の株価の一覧を表示する画面と画面で動作するスクリプトを実装します。ここではHTML(index.html)とスクリプト(public/index.html.js)の実装のみ説明します。CSS(index.html.css)については説明を割愛しますので、詳しくはソースコードをご確認下さい。

まずは画面に紐づくスクリプトの実装を行います。

株式情報一覧画面のスクリプト(public/index.html.js)

// Functionsが起動しているURLを指定
const LOCAL_BASE_URL = 'http://localhost:7071';

const app = new Vue({
    el: '#app',
    data() { 
        return {
            stocks: []
        }
    },
    methods: {
        // 株価一覧を取得する ・・・①
        async getStocks() {
            try {
                const apiUrl = `${LOCAL_BASE_URL}/api/getStocks`;
                const response = await axios.get(apiUrl);
                console.log('Stocks fetched from ', apiUrl);
                app.stocks = response.data;
            } catch (ex) {
                console.error(ex);
            }
        }
    },
    created() {
        this.getStocks();
    }
});

const connect = () => {
    // SignalR Serviceインスタンスと接続する ・・・②
    const connection = new signalR.HubConnectionBuilder().withUrl(`${LOCAL_BASE_URL}/api`).build();

・・・中略

    // 株価の更新情報をSignalR Serviceインスタンスから受信して画面に反映する ・・・③
    connection.on('updated', updatedStock => {
        const index = app.stocks.findIndex(s => s.id === updatedStock.id);
        app.stocks.splice(index, 1, updatedStock);
    });

・・・中略
};

connect();

このスクリプトではVue.jsを使用しています。Vueインスタンスの生成時に株価の一覧をFunctionsのgetStocks関数から取得するようにしています(①)。これで画面にアクセスしたタイミングですべての株価情報を確認することができます。その後、株価に変動があった場合はSignalR Service経由でリアルタイムに更新内容を画面に反映できるようにしています(②、③)。
②の箇所でまずSignalR Serviceインスタンスとの接続を開始します。ここでは内部的にFunctionsの「negotiate」関数が呼び出され、クライアントとSignalR Serviceインスタンスとの間で接続が確立されます。株価データの更新情報は、SignalR Serviceから③のイベントリスナーに連携されます。イベントリスナー内では、受信した内容をもとに画面表示を更新しています。

続いて画面の実装を行います。

株式情報一覧画面(public/index.html)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.8.0/css/bulma.min.css">
    <link rel="stylesheet" href="index.html.css">
    <title>Stock Service</title>
  </head>
  <body>
    <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>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@aspnet/signalr@1.1.0/dist/browser/signalr.js"></script> ・・・②
    <script src="index.html.js" type="text/javascript"></script> ・・・①
  </body>
</html>

HTMLの実装ではまず、scriptタグを使って先程作成したスクリプト(①)とAzure SignalR Serviceが提供するクライアントSDKをインポートします。HTMLがクライアントSDKを読み込むことで、negotiate関数を呼び出してSignalR Serviceインスタンスとの接続を確立する仕組みとなっています。一度接続が確立されると画面を閉じるまではSignalR Serviceインスタンスとの接続は継続され、リアルタイム通信ができるようになります。
画面の実装のメイン部分(③)では、Vue.jsの記法を用いながら株式情報の一覧を表示しています。

アプリケーションを実行してみよう

ここまでアプリケーションの各コンポーネントの実装を行ってきました。ここからは実際にアプリケーションを起動し、SignalR Serviceからのデータ更新が画面に反映される様子を確認してみましょう。

package.jsonの修正

アプリケーションを起動する前に、まずはpackage.jsonの修正をします。「scripts」の中身を以下のように書き換えます。

scriptsの修正(package.json)

{
  ・・・中略
  "scripts": {
    "start": "lite-server --baseDir=\"public\"",
    "setup": "node setup.js",
    "update-data": "node update.js"
  },
  ・・・中略
}

「scripts」内に記載したコマンド名は、「npm run」の後ろに指定することで記載しているコマンドを実行することができるようになります。

Functionsの起動

はじめにFunctionsをローカルで起動します。前回同様Azure Functionsのコマンドラインツールを使用してFunctions用のサーバーを起動します。ターミナルを開き、プロジェクトのルートディレクトリ上で以下のコマンドを実行します。

Functionsをローカルで起動する

$ func start

・・・中略
# 以下のメッセージが表示されれば起動成功
Generating 3 job function(s)
Found the following functions:
Host.Functions.negotiate
Host.Functions.stocksChanged
Host.Functions.getStocks
・・・中略
Job host started

起動に成功すると、HTTPトリガーで起動する関数は「http://localhost:7071」上にホスティングされます。

アプリケーションの起動

続いてアプリケーションの起動をします。Functionsを起動したものとは別のターミナルを用意し、プロジェクトのルートディレクトリ上で以下のコマンドを実行します。

アプリケーションを起動する

# セットアップ用のスクリプトの実行 ・・・①
npm run setup

# アプリケーションの起動 ・・・②
npm start

①では「setup.js」に実装した処理が実行されます。Cosmos DBアカウントにデータベースとコンテナを作成し、サンプルの株価データを3件追加しています。 ②の実行後、ブラウザで以下のように株価一覧が表示されれば起動は成功です。

3つの銘柄の株価とそれぞれの直近の値動きが表示されています。

動作確認用スクリプトの作成

アプリケーションが起動できたら、Cosmos DBに登録されている株価データの更新をしてその内容がSignalR Serviceを経由して画面上にリアルタイムに反映されることを確認してみましょう。新しいターミナルをもう1つ開き、プロジェクトのルートディレクトリ上で以下のコマンドを実行します。

Functionsをローカルで起動する

npm run update-data

コマンドを実行した直後に、ブラウザ上に表示されている「ABC」という銘柄の株価が更新されていることが分かります。上記のコマンドを実行するたびに、画面上のデータが更新されていくことが確認できます。

上記のコマンド実行時には、「update.js」というスクリプトが実行されます。この中で「ABC」の銘柄の株価をランダムに変更し、Cosmos DB上のデータを更新する処理を実装しています。このコマンドを実行することよりCosmos DB上のデータ更新をトリガーにFunctionsの「stocksChanged」関数が実行され、それがSignalR Serviceインスタンスを経由して画面に反映されているのです。 なお「update.js」の実装については割愛しますので、詳しくはソースコードをご確認下さい。

まとめ

全3回に渡ってAzure SignalR Serviceについて説明してきました。Azure SignalR Serviceをアプリケーションに適用することで旧来の擬似的なリアルタイムアプリケーションの仕組みから、本格的なリアルタイムアプリケーションの構築にシフトできることが分かったかと思います。Azure FunctinsやAzure Cosmos DBなど他のAzureサービスとの連携も容易なため、サーバーレスなアプリケーションを構築する際のキーポイントとしてAzure SignalR Serviceの利用を検討してみる価値があるかと思います。

次回は、2020年5月に正式版がリリースされたWindows Terminalについて説明する予定です。

WINGSプロジェクト 秋葉龍一著/山田祥寛監修
<WINGSプロジェクトについて>テクニカル執筆プロジェクト(代表山田祥寛)。海外記事の翻訳から、主にWeb開発分野の書籍・雑誌/Web記事の執筆、講演等を幅広く手がける。一緒に執筆をできる有志を募集中