November 2015

Volume 30 Number 12

データ ポイント - Aurelia と DocumentDB: 連携までの道のり

Julie Lerman | 年 11 月 2015

Julie Lermanここ数か月のコラムに続いて、今回も未知の世界に踏み込みます。9 月のコラムでは、Aurelia という新しい JavaScript クライアント フレームワークにおけるデータ バインドを取り上げました。Aurelia は、バックエンド ASP.NET 5 Web API と通信して、データの取得と格納を行います。この Web API は、背後で SQL Server と Entity Framework を使用していました。6 月のコラムでは、DocumentDB という新しい Microsoft Azure NoSQL データベース サービスを調べました。そのとき、ASP.NET MVC 5 Web API もビルドしましたが、この Web API は、DocumentDB .NET クライアント ライブラリを使用して DocumentDB データベースと通信していました。

そこで、今度は Aurelia と DocumentDB を連携させることを考えました。この連携は、困難な道のりとなり、問題にも直面しましたが、なんとか Aurelia と DocumentDB の通信を可能にする適切な方法にたどり着くことができました。今回は、そこにたどり着くまでに学んだ教訓と、ようやくたどり着いたソリューションの概要を説明します。そのソリューションの詳細については、次回以降のコラムで取り上げる予定です。

最後にたどり着いたソリューションから多くのことを学習できますが、そこにたどり着くまでに試した手順は (失敗した手順であっても) 1 つの教訓になります。

まず、連携の計画について意識を合わせておきます。この計画は、JavaScript、および 多くの API、ツール、パターンの経験不足を感じさせないもので、当初は目標を実現できる優れた計画だと思っていました。正しい道筋を教えるのは簡単ですが、この道筋を再検討してさらに優れた方法を見つけようとすると、私と同じように行き詰まることになるだけです。そこで、もちろん、正しい道筋は示しますが、この連携までの道筋の全容を連載コラムとしてお伝えしようと思います。

最善の配置計画: 「どの程度困難になり得るか」

Azure DocumentDB は 1 つのサービスです。そのため、このサービスとの対話そのものは、SQL 構文または RESTful HTTP 呼び出しのいずれかを使用して行います。前回は、そのような低レベルの操作ではなく、多くの API の中からマイクロソフトが DocumentDB 用に作成した .NET API を利用しました。これにより、以下のような LINQ を使用してデータベースに対するクエリを表現し、送信できるようになります。

return Client.CreateDocumentQuery(Collection.DocumentsLink)
       .Where(d => d.Id == id)
       .AsEnumerable()
       .FirstOrDefault();

そのとき、この作業を自動的に行う Web API を作成しました。

そのコラムでの Aurelia データ バインドのデモでは、Entity Framework を使用する別の Web API への HTTP 呼び出しを簡単に行って、SQL Server データベースにアクセスしました。この Web API を書き換えれば、DocumentDB と対話する Web API を作成できます。こののシナリオなら私の仕事は簡単に終わり、今までで一番短いコラムになります。それでは意味がありません。

そこで、DocumentDB が提供する直接的な REST 操作を使用する方が面白くなると考えました。この考えは、非常にシンプルに思えました。DocumentDB のドキュメントには、DocumentDB に対して行う HTTP 要求の例がたくさん示されており、これを利用できると考えました。図 1 に例を 1 つ示します。

図 1 DocumentDB に対する HTTP 要求の例

POST https://contosomarketing.documents.azure.com/dbs/XP0mAA==/colls/XP0mAJ3H-AA=/docs HTTP/1.1
x-ms-DocumentDB-isquery: True
x-ms-date: Mon, 18 Apr 2015 13:05:49 GMT
authorization: type%3dmaster%26ver%3d1.0%26sig[A HASH GOES HERE]
x-ms-version: 2015-04-08
Accept: application/json
Content-Type: application/query+json
Host: contosomarketing.documents.azure.com
Content-Length: 50
{
 query: "SELECT * FROM root WHERE (root.Author.id = 'Don')",
 parameters: []
}

しかし、それほど簡単ではありませんでした。まず、authorization に設定するマスター キーをクライアント アプリケーション内部から提供することは考えられません。さらに、このキーは本当のマスター キーではありません。Azure ポータルの DocumentDB の設定から取得したキーで、キーのハッシュと追加情報です。この文字列を構築する方法は、「DocumentDB リソースのアクセス制御」(bit.ly/1O9dBfP、英語) に記載されていますが、そこに記載されている方法を使用しても、このような API 呼び出しを Fiddler でテストすることはできませんでした。

しかし、もっと大きな問題は、いずれにしても、クライアント アプリケーションでマスター キーを設定するわけにはいかないことです。「DocumentDB のデータへのアクセスのセキュリティ保護」(https://azure.microsoft.com/ja-jp/documentation/articles/documentdb-secure-access-to-data/) によると、推奨アーキテクチャはミドルウェアを作成する方法だそうです。このミドルウェアで、マスター キーに安全にアクセスし、クライアント側アプリケーションが使用するリソース キーを作成して返します。

そこで、このようなミドルウェアを作成することにしました。DocumentDB 用の .NET クライアントを使用して ASP.NET Web API をビルドし、要求に応じて適切に構成したリソース キーを返します。これには、前回の DocumentDB のコラムに掲載したのと同じセットアップ コードが必要です。つまり、Azure アカウント、データベース クライアント、クライアント内のデータベース、およびそのデータベース内のコレクションを定義します。前回はその上に 85 行のコントローラーコードを用意しました。そのコードでは、データベースに接続し、ユーザーをチェックして、そのユーザーに実行させる操作の権限の確認、削除、再作成を行い、その後、リソース トークンを生成し、ハッシュ化して、要求側のクライアントに返します。これは、理解してセットアップするにはやや複雑なコードです。

このサービスを用意したら、次は Aurelia アプリからこのサービスを呼び出して、特定の操作のためにトークンを取得し、その操作でトークンを再利用します。長期的に見れば、Windows Communication Foundation (WCF) サービスに使用するセキュリティ操作と大差ありません。しかし、これはサーバーとクライアントの間のやりとりが非常多くなるソリューションです。結局のところ、DocumentDB によってますます複雑になるため、生成したリソース トークンを使ってクライアント側 (JavaScript) で正しい要求を構築することができませんでした。DocumentDB は、このように構築したデータ取得要求の承認を拒否しました。したがって、HTTP 経由でクライアント アプリから直接 DocumentDB に対して独自の RESTful 呼び出しを行っても成功しないと判断しました。ただし、DocumentDB が進化するにつれて、DocumentDB への接続オプションも進化すると予想されるため、いずれこのアイデアを再検討することを計画しています。

まだ解決の糸口は残されています。DocumentDB には JavaScript SDK もあります。この SDK は、高レベルの手法を使用したとしても、DocumentDB に対する RESTful 呼び出しを自動的に構築する方法を把握します。今回作成した ResourceTokenGenerator Web API から要求したリソース トークンを使用して、要求を自動作成させることができるという理解に基づいて、この SDK をクライアント ソリューションに導入しました。正しい方向に進んでいるように思えましたが、クロス オリジン リソース共有 (CORS) を有効にする方法がないという新たな問題に直面しました。つまり、あるドメインのクライアント側アプリケーションから別のドメインのサービスを呼び出すことができません。

この時点で、万策尽き果て、ラッパーなしで RESTful 呼び出しを行うという目論見ははずれました。しかし、単純に既存の Web API に戻って Aurelia アプリを DocumentDB と通信させるわけにもいかないので、視点を変えることにしました。

成功への道筋: DocumentDB、Express、Aurelia、および Node.js

DocumentDB は、.NET や JavaScript のクライアント SDK 以外に、Node.js SDK (bit.ly/1LifOa1、英語) も提供します。これにより、Node.js (サーバー側で機能する、ASP.NET 分離コードのロジックによく似た JavaScript 実装) を使用して、DocumentDB に簡単にアクセスできるようになります。構成、認証、RESTful API 呼び出しのビルドなど、難しい処理はすべて SDK のメソッドにラップされています。そこで、Aurelia アプリケーションと DocumentDB を対話させるために、この道筋を進むことにしました。しかし、新しい学習課題がたくさんあります。個人的には Node.js に触れたこともありません。JavaScript については自他共に認める初心者です。Express という追加の API も関係します。Express API は、Node.js の使用を容易にする中核機能を多数ラップしています。それだけではありません。Aurelia に大きく踏み込むには、コマンド ラインでの作業に慣れなくてはならず、メモ帳よりもはるかに Web 開発に特化した SublimeText テキスト エディターに慣れることも必要です。前回のアプリの操作の大半はクライアント側にあるため、ブラウザーがあればすぐにデバッグできます。しかし、今回はサーバー側の Node.js コードをデバッグすることになります。こうしたデバッグには、Visual Studio ファミリに新しく追加された Visual Studio Code が優れたツールになるとわかりました。

さいわい、2 つの重要なサンプルを参考になります。DocumentDB 側には、Node.js と DocumentDB を使用する小さな Web アプリケーションをビルドするためのチュートリアルがあります (https://azure.microsoft.com/ja-jp/documentation/articles/documentdb-nodejs-application/)。Aurelia 側としては、GitHub に、Node.js を使用する Aurelia アプリのスケルトンをセットアップするリポジトリがあり、既にサーバー側のロジックが統合されています (bit.ly/1XkMuEX、英語)。

今回のソリューションを実装するには、Node.js SDK を使用するサンプルの基本メカニズムを理解する必要があります。ここからは、大まかなチュートリアルでは触れられていない細かい情報を紹介し、最後にたどり着いたソリューションでの API の使用方法を解説します。

DocumentDB Node.js のチュートリアルでは、DocumentDB Node.js SDK を使用して DocumentDB と通信する、Node.js のバックエンド ロジックが説明されています。このロジックの冒頭部分は 1 組の汎用ユーティリティで、データベース接続のインスタンスの作成、必要に応じた Database First の作成、およびデータベース内の操作対象となる特定のコレクションのインスタンスの作成を行っています。このユーティリティは、任意の DocumentDB と通信するアプリケーションで再利用できます。それは、DocumentDB により、開発者は認証情報などの情報を渡して、操作するデータベースやコレクションを指定できるようになるためです。

このユーティリティ クラスは、ソリューションに既にインストールしている Node.js 用 DocumentDB SDK への参照を設定するところから始まります。

var DocumentDBClient = require('DocumentDB').DocumentClient;

次に、関連する接続情報をパラメーターとして受け取るメソッドを設定します。たとえば、以下のようにまず、getOrCreateDatabase メソッドで始まるクラスを宣言します。このメソッドでは、最初に Azure からデータベースを取得するクエリを定義し、SDK DocumentClient クラスの queryDatabases メソッドを使用してそのクエリを実行します。結果 (results) が空の場合は、別の呼び出し (ここには記載していません) でデータベースを作成します。queryDatabases メソッドは、取得したデータベースのインスタンスを返します。DocDBUtils クラスの完全な一覧は、参照記事 (https://azure.microsoft.com/ja-jp/documentation/articles/documentdb-nodejs-application/) で確認できます。

var DocDBUtils = {
 getOrCreateDatabase: function (client, databaseId, callback) {
  var querySpec = {
   query: 'SELECT * FROM root r WHERE r.id=@id',
   parameters: [{
    name: '@id',
    value: databaseId
   }]
  };
  client.queryDatabases(querySpec).toArray(function (err, results) {
  // Additional logic to specify callbacks and more

ロジックの 2 つ目のブロックは、tasklist.js という、いわゆるコントローラーです。このコントローラーでは、DocDBUtils クラスによって提供されるデータベースやコレクションのインスタンスを使用するメソッドを提供し、データを操作できるようにします。このコントローラーは、Task という ToDo 項目を格納および取得するサンプル専用に設計されています。Task オブジェクトは TaskDao というクラスにカプセル化されているため、TaskDao のインスタンスへの参照が controller クラスにあるのがわかります。controller クラスには、Task を取得するメソッドだけでなく、新しい Task を追加するメソッドも、Task を更新および削除するメソッドもあります。このクラスは、DocumentDB SDK への参照と、先ほど説明したユーティリティ クラスから始まります。

var DocumentDBClient = require('DocumentDB').DocumentClient;
var docdbUtils = require('./docdbUtils');

Tasklist.js には、showTasks や addTask などの関数も含まれています。これらの関数は、要求オブジェクトと応答オブジェクトをパラメーターとして受け取る Node.js の表記法に従い、Node.js が別のプロセスにしたがってブラウザーから要求を渡せるようにし、ブラウザーに返す応答に含める情報を挿入できるようにします。図 2 に、showTasks 関数を示します。

図 2 tasklist.js controller クラスの showTasks コマンド

showTasks: function (request, response) {
 var self = this;
 var querySpec = {
  query: 'SELECT * FROM root r WHERE r.completed=@completed',
  parameters: [{
   name: '@completed',
   value: false
  }]
 };
 self.taskDao.find(querySpec, function (err, items) {
  if (err) {
   throw (err);
  }
  response.render('index', {
   title: 'My ToDo List ',
   tasks: items
  });
 });
},

このサンプルでは、Express という追加のライブラリを使用していることに注意してください。Express は、Node.js の機能をさらに大まかなメソッドにラップします。showTasks 関数は、response オブジェクトの Express render メソッドを使用して、DocumentDB から取得した items を Index.html ファイルで利用可能な tasks プロパティに渡して、index ビュー (index.html など) にレンダリングしているのがわかります。

コントローラー (TaskList クラス) は、Web サイトのルーティングに応答するときの、サーバー側 Node.js ロジックへの最初のエントリ ポイントです。コントローラー メソッドのロジックでは、showTasks 関数にある self.taskDao.find への呼び出しで示されているように、taskDAO オブジェクトを使用してクエリや更新をトリガーします。taskDAO には、利用するデータベースやコレクションを DocDbUtils を使用して設定する init 関数があります。これらを用意したら、DocumentDB SDK を使用して、find 関数、getitem 関数、および updateItem 関数で直接クエリや更新を定義および実行できます (図 3 参照)。

DocumentDb Node.js サンプル アプリケーションのクラスと SDK の依存関係ワークフロー
図 3 DocumentDb Node.js サンプル アプリケーションのクラスと SDK の依存関係ワークフロー

Node.js でバックエンド ロジックを設定したら、次はフロント エンドをビルドします。DocumentDB サイトのチュートリアルでは、Jade というビュー生成フレームワークが使用されています。Jade API を使用してセットアップしたビューとルーティングの HTML ファイルにより、UI はサーバーの taskList コントローラーを呼び出して、ユーザーのナビゲーション要求に応答できるようになります。サーバー側の taskList コントローラーでは、DocumentDB キーを安全に格納して、データ操作を承認します。

次のステップ: Node.js バックエンドへの Aurelia のフック

ただし、今回の目標はクライアント フレームワークとして Jade ではなく Aurelia を使用することでした。次は、DocumentDB Node.js SDK の使用方法を学習し、GitHub の Aurelia ノードのサンプルで提供されている Node.js 対応スケルトン アプリケーションにその知識を当てはめることです。ですが、Aurelia のルーティングの機能は Jade のルーティングとはやや異なるため、パズルの 2 つのピースを合わせるような簡単な問題ではありません。Node.js と Express の経験不足に加えて、JavaScript については「危険を察知できる程度の」大雑把なスキルしかないので、問題は想像以上に複雑になりました。しかし、Aurelia チームの中心メンバーからの支援を受け、最終的にはすべてやり遂げることができました。

次回は、コントローラーと Aurelia ルーティング間の重要なコネクターについて説明し、サーバー側の Node.js ソリューションを使用して DocumentDB と通信する方法を取り上げ、Aurelia から Web API への直接 HTTP 呼び出しの簡単さと比べます。


Julie Lermanは、バーモント ヒルズ在住の Microsoft MVP、.NET の指導者、およびコンサルタントです。世界中のユーザー グループやカンファレンスで、データ アクセスなどの .NET トピックについてプレゼンテーションを行っています。彼女のブログは thedatafarm.com/blog (英語) で、彼女は O'Reilly Media から出版されている『Programming Entity Framework』(2010 年) および『Code First』版 (2011 年)、『DbContext』版 (2012 年) を執筆しています。彼女の Twitter (@julielerman、英語) をフォローして、juliel.me/PS-Videos (英語) で彼女の Pluralsight コースをご覧ください。

この記事のレビューに協力してくれた技術スタッフの Ryan CrawCour および Patrick Walters に心より感謝いたします。
Ryan CrawCour は、20 年にわたりデータベースに携わるベテランです。はるか昔、SQL Server 4.2 で初めてストアド プロシージャの作成に着手しました。多くのカーソル、結合、ストアド プロシージャなどを経て、NoSQL ソリューションの自由で魅力的な世界の探求を始めました。

Patrick Walters は、Aurelia 開発者の優秀なコミュニティの一員で、公式の Aurelia Gitter チャンネルで楽しく質問に答えています。