はじめに

今回は前回に引き続きAzure SignalR Serviceの紹介をします。より実践的なアプリケーションの構成におけるAzure SignalR Serviceの用途について実装を行いながら説明していきます。

リアルタイムアプリケーションを実装しよう

今回作成するのは、現在の株式情報の一覧を表示するアプリケーションです。各株式の銘柄と株価、直近の値動きを表示し、株価に変動があった場合には表示されている内容を更新できるようにします。

  • 今回作成するアプリケーションの画面

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

株価に変動があった際にその内容を画面にリアルタイムに反映するための仕組みとして、Azure SignalR Serviceを使用していきます。またAPIにあたる箇所はAzure Functionsの関数として定義し、株式情報はAzure Cosmos DBに登録します。これらのAzureサービスを連携してリアルタイムアプリケーションを実装していきます。

サンプルソースコードについて

今回実装するソースコードについては、ここからダウンロードできます。 以降ではこの中からポイントとなる点を抜粋しながら説明をしていきます。

プロジェクトの作成

作成するアプリケーションは前回と同様にNode.jsのプロジェクトとして実装していきます。 まずはNode.jsのプロジェクトを作成し、必要な依存ライブラリをインストールしていきます。任意の場所に新しいディレクトリを作成し、その中で以下のコマンドを実行します。

Node.jsプロジェクトの作成と依存ライブラリのインストール

# Node.jsプロジェクトの作成
npm init -y

# 依存ライブラリのインストール
npm install @azure/cosmos@2.1.5 --save
npm install lite-server --save-dev

依存ライブラリとして、Azure Cosmos DBのクライアントライブラリと簡易的なサーバーを起動できる「lite-server」をインストールします。

Azure Cosmos DBアカウントの作成

次にアプリケーションのデータベースとして使用するAzure Cosmos DBのアカウントを作成していきます。Azureポータルにログインして画面上部の検索バーで「cosmosdb」と入力してCosmos DBの画面を表示し、「追加」ボタンあるいは「Azure Cosmos DBアカウントの作成」ボタンから新しいCosmos DBアカウントを作成していきます。

  • Azure Cosmos DBのアカウント一覧画面

    Azure Cosmos DBのアカウント一覧画面

Cosmos DBアカウントの新規作成画面が表示されたら、各項目の入力を行います。「サブスクリプション」と「リソースグループ」はお手持ちのものを入力していきます。「アカウント名」には任意のCosmos DBアカウント名を入力します。「API」ではAzure Cosmos DBが提供するデータベースの種類を選択することができます。今回は「コア(SQL)」を選択します。「場所」はここでは「東日本」を選択しています。「Apply Free Tier Discount」は「Apply」にします。この項目を「Apply」にすると指定の利用範囲まではCosmos DBの利用料金を無料にすることができます。ただし、サブスクリプションに対して1つのみ適用できるものとなっています。今回のようなサンプルの実装や開発時にCosmos DBを使用したい場合などにコストを抑えることができるようになっています。その他の項目についてはデフォルトのまま、「確認と作成」ボタンを選択して次の画面で「作成」ボタンを選択し、Cosmos DBアカウントの作成を開始します。

  • Cosmos DBアカウントの新規作成

    Cosmos DBアカウントの新規作成

しばらく待って作成完了の通知が表示されればCosmos DBアカウントの作成は完了です。

SignalR Serviceインスタンスの作成

続いてはSignalR Serviceインスタンスの作成です。SignalR Serviceインスタンスについては、前回作成したものを今回も引き続き利用していきます。もし、まだ作成されていない場合は前回の内容をもとにインスタンスを作成して下さい。

接続情報をアプリケーションに設定する

Cosmos DBアカウントとSignalR Serviceインスタンスの作成が完了したら、両サービスの接続情報をアプリケーションに設定していきます。プロジェクトのディレクトリ直下に「local.settings.json」ファイルを作成し、以下のように記述します。

接続情報を設定ファイルに記述する(local.settings.json)

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "<STORAGE_CONNECTION_STRING>",
    "AzureCosmosDBConnectionString": "<Cosmos DBアカウントのプライマリ接続文字列>", ・・・①
    "AzureCosmosDBMasterKey": "<Cosmos DBアカウントのプライマリキー>", ・・・②
    "AzureSignalRConnectionString": "<SignalR Serviceインスタンスのプライマリ接続文字列>", ・・・③
    "FUNCTIONS_WORKER_RUNTIME": "node"
  },
  ・・・中略
}

①と②にはCosmos DBアカウントの接続情報をそれぞれ入力します。Azureポータルで、先ほど作成したCosmos DBアカウントを表示し、「キー」というメニュー内にある「プライマリ接続文字列」と「プライマリキー」をコピーしてjsonに貼り付けます。

  • Cosmos DBアカウントの接続情報をコピーする

    Cosmos DBアカウントの接続情報をコピーする

③には同様にSignalR Serviceインスタンスの接続情報を入力します。AzureポータルでSignalR Serviceインスタンスの画面に移動し、「Keys」メニュー内の「Primary」にある「CONNECTION STRING」の内容をコピーして③に貼り付けます。

  • SignalR Serviceインスタンスの接続情報をコピーする

    SignalR Serviceインスタンスの接続情報をコピーする

local.settings.jsonの編集は以上です。なお「AzureWebJobsStorage」という項目については今回は設定は不要です。この項目はFunctionsをAzure Functions上にデプロイする際に必要となるストレージアカウントの接続情報を入力しますが、ローカルでの実行時にはストレージアカウントは使用しないため、デフォルトのままとします(項目自体を削除するとローカルマシンであってもFunctionsが起動しないため、項目は残したままにします)。

Cosmos DBアクセス用モジュールの作成

続いてAzure Cosmos DBのデータにアクセスするためのモジュールを作成していきます。

Cosmos DBにアクセスするクライアント(db.js)

const cosmos = require('@azure/cosmos');
const CosmosClient = cosmos.CosmosClient;
const fs = require('fs');

// Cosmos DBの接続情報を設定ファイルから取得 ・・・①
const settings = JSON.parse(fs.readFileSync('local.settings.json', 'utf8'));

let endpoint = '';
let client = null;

const masterKey = settings.Values.AzureCosmosDBMasterKey;
const matches = settings.Values.AzureCosmosDBConnectionString.match(/(https.*?);/);

if(matches && matches.length > 1) {
    endpoint = matches[1];
    // Cosmos DBクライアントの生成・・・②
    client = new CosmosClient({ endpoint, auth: { masterKey } });
} else {
    console.log('Cannot locate Cosmos DB endpoint from connection string.');
}

module.exports = client;

このモジュールはプロジェクトの依存ライブラリとしてインストールしたCosmos DBクライアントライブラリを使用し、Cosmos DBアカウントに接続するクライアントのインスタンスを生成しています(②)。クライアントに渡す情報として先ほど設定したlocal.setting.jsonに記載してあるCosmos DBアカウントの接続情報を参照しています(①)。

セットアップモジュールの作成

続いてアプリケーションの起動時に実行されるセットアップ用のモジュールを作成します。このモジュールでは、株価のサンプルデータ数件をCosmos DBに登録しています。

アプリケーションの起動用モジュール(setup.js)

const client = require('./db.js');

const databaseDefinition = { id: "stocksdb" };
const collectionDefinition = { id: "stocks" };

const setupAndSeedDatabase = async ()  => {

  // Cosmos DBアカウント内にデータベースを作成する ・・・①
  const { database: db } = await client.databases.createIfNotExists(databaseDefinition);
  console.log('Database created.');

  // データベース内にコレクションを作成する ・・・②
  const { container } = await db.containers.createIfNotExists(collectionDefinition);
  console.log('Collection created.');

  // サンプルの株価データを登録する ・・・③
  await container.items.create({
    "id": "e0eb6e85-176d-4ce6-89ae-1f699aaa0bab",
    "symbol": "ABC",
    "price": "100.00",
    "change": "1.00",
    "changeDirection": "+"
  });

  ・・・中略

  console.log('Seed data added.');
};

setupAndSeedDatabase().catch(err => {
  console.error('Error setting up database:', err);
});

このモジュールではファイルの冒頭で先ほど作成したdb.jsから参照しているCosmos DBクライアントを使ってCosmos DBに対してデータの操作を行っています。①と②ではデータの保存先となるデータベースとコレクションを作成してます。③で作成したコレクション内に株価のサンプルデータを追加しています。サンプルコードでは3件のデータが登録されるようにしています。

株価情報全件を取得するFunctionsの実装

次はFunctionsの実装を行っていきます。今回は「getStocks」という関数を作成します。この関数はHTTPリクエストに応じてCosmos DBに保存しているすべての株価情報を取得・返却する関数として実装します。 プロジェクトのルートディレクトリに「getStocks」というディレクトリを作成し、その中で「functions.json」と「index.js」というファイルを作成していきます。

株式情報一覧取得の関数定義(getStocks/function.json)

{
  "bindings": [
    { ・・・①
      "authLevel": "anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": ["get"]
    },
    { ・・・②
      "type": "http",
      "direction": "out",
      "name": "res"
    },
    { ・・・③
      "type": "cosmosDB",
      "direction": "in",
      "ConnectionStringSetting": "AzureCosmosDBConnectionString",
      "name": "stocks",
      "databaseName": "stocksdb",
      "collectionName": "stocks"
    }
  ]
}

まずは関数定義ファイル(function.json)を作成します。この関数定義ファイルでは3つの入出力定義を記述しています。①ではHTTP GETメソッドによるHTTPリクエストをトリガーにこの関数を起動する定義が記述されています。②は関数の処理結果をHTTPレスポンスとして返却する定義です。これらに加えて③では、関数が呼び出された時にCosmos DBにアクセスしてデータを取得するための定義を記述しています。Azure Functionsでは「type」を「cosmosDB」とすることでCosmos DBとの入出力をサポートします。定義内の「ConnectionStringSetting」、「databaseName」、「collectionName」の3項目によって接続するCosmos DBアカウント、データベース、コレクションを具体的に決定しています。

株式情報一覧取得関数(getStocks/index.js)

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

関数本体は「index.js」に実装します。関数の引数「stocks」として渡ってくる、Cosmos DBから取得したすべての株価情報をそのままHTTPのレスポンスとして渡しています。これによって関数を呼び出したクライアント側では株価一覧を表示することができるようになります。

ここまでの実装の動作確認をしよう

ここまでの実装内容で一度動作確認をしてみます。セットアップスクリプトを実行してCosmos DBに株式情報のデータが登録され、Functionsの関数で同様の内容が取得できることを確認します。

まずセットアップスクリプトを実行するために、package.jsonの修正をします。「scripts」の中身を以下のように書き換えます。

scriptsの修正(package.json)

{
  ・・・中略
  "scripts": {
    "setup": "node setup.js"
  },
  ・・・中略
}

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

セットアップスクリプトの実行

$ npm run setup

コマンドが実行できたら、AzureポータルからCosmos DB上にデータが登録されたことを確認します。Azureポータルで作成したCosmos DBアカウントを表示し、「データエクスプローラー」を選択します。

  • Cosmos DBのデータエクスプローラー

    Cosmos DBのデータエクスプローラー

データエクスプローラーの画面に、「stocksdb」というデータベースがあり、その中に「stocks」というコレクションが作成されていることが分かります。「stocks」コレクションを展開して「Items」を選択すると、テストデータとして登録した株式情報が3件存在することも確認することができます。

Cosmos DBにデータが登録されていることが確認できたら、Azure Functionsを起動して「getStocks」関数を呼び出してみましょう。Azure Functionsをローカルで起動するために以下のコマンドをコンソールで実行します。

Azure Functionsをローカルで起動する

$ func start

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

・・・中略

Http Functions:

        getStocks: [GET] http://localhost:7071/api/getStocks

コマンド実行に成功すると、「getStock」関数を実行するためのURLが表示されます。このURLをブラウザに貼り付けて、以下のようなJSONが返却されればFunctionsの実行は成功です。

  • getStocks関数の実行結果

    getStocks関数の実行結果

表示されている内容は、先ほどCosmos DBのデータエクスプローラーで表示した「Items」内の各株式情報の内容と同じものとなっているはずです。これでFunctionsとCosmos DBの連携がとれていることが確認できました。

まとめ

今回は株式情報の一覧を表示するアプリケーションを題材に、Azure SignalR Serviceを使ったリアルタイムアプリケーションの実装方法について説明しました。今回はSignalR Serviceをアプリケーションに組み込む実装の前段階として、プロジェクトの新規作成からAzure Cosmos DBとAzure Functionsの連携部分までを紹介しました。次回は、このアプリケーションにAzure SignalR Serviceを追加してリアルタイムアプリケーションとしての実装を完成させていく予定です。

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