Google Base と Google Gears を使用してオフラインのパフォーマンスを高める

「Google API を使用したより優れた Ajax アプリケーションの構築」シリーズの最初の記事です。

Dion Almaer および Pamela Fox、Google
2007 年 6 月

編集者注: Google Gears API はご利用いただけなくなりました

はじめに

Google Base と Google Gears を組み合わせて、オフラインで使用できるアプリケーションを作成する方法を示します。この記事を読むと、Google Base API をよく理解できるだけでなく、ユーザーの設定やデータの保存とアクセスに Google Gears を使用する方法もわかります。

アプリについて

このアプリを理解するには、まず Google Base について理解する必要があります。Google Base は、商品、レビュー、レシピ、イベントなど、さまざまなカテゴリにまたがるアイテムの大規模なデータベースです。

各アイテムには、タイトル、説明、データの元のソース(存在する場合)へのリンク、およびカテゴリのタイプごとに異なるその他の属性でアノテーションが付けられています。Google Base では、同じカテゴリのアイテムに共通の属性セットが使用されているという事実を活用しています。たとえば、すべてのレシピに材料があります。Google Base のアイテムは、Google ウェブ検索や Google 商品検索の検索結果に表示されることもあります。

デモアプリ「Base with Gears」を使用すると、Google Base でよく使う検索ワード「チョコレート」(yum)や、個人広告「ウォーク オブ ザ ビーチ」(ロマンチック!)などの一般的な検索の結果を保存し、表示できます。「Google Base リーダー」と考えることができます。Google Base リーダーは、検索をサブスクライブして、アプリを再訪問したとき、または更新されたフィードを 15 分ごとに調べるときに、更新された結果を確認できます。

アプリを拡張しようとするデベロッパーは機能を追加できます。たとえば、検索結果に新しい検索結果が表示されたときにユーザーに視覚的にアラートを出し、お気に入りのアイテムをブックマーク(オフライン + オンライン)できるようにする、Google Base のようなカテゴリ固有の属性検索を行うなどです。

Google Base Data API フィードの使用

Google Base は Google Data API フレームワークに準拠した Google Base Data API を使用して、プログラムでクエリできます。Google Data API プロトコルは、ウェブでの読み取りと書き込みを行うためのシンプルなプロトコルで、Picasa、スプレッドシート、Blogger、カレンダー、ノートブックなど、多くの Google サービスで使用されます。

Google Data API の形式は XML と Atom Publishing Protocol に基づくため、読み取り/書き込みの操作のほとんどは XML です。

Google Data API に基づく Google ベースフィードの例:
http://www.google.com/base/feeds/snippets/-/products?bq=digital+camera

snippets フィードタイプは、一般公開されているアイテムのフィードを提供します。-/products を使用すると、フィードを商品カテゴリに制限できます。また、bq= パラメータを使用すると、「デジタルカメラ」というキーワードを含む結果のみにフィードをさらに制限できます。このフィードをブラウザで表示すると、一致する結果が含まれる <entry> ノードを含む XML が表示されます。各エントリには、典型的な著者、タイトル、コンテンツ、商品リンクの要素が含まれるだけでなく、カテゴリ固有の属性(商品カテゴリの「価格」など)も登録されます。

ブラウザでの XMLHttpRequest のクロスドメイン制限により、JavaScript コードで www.google.com の XML フィードを直接読み込むことはできません。XML を読み取るようにサーバー側のプロキシを設定して、それをアプリと同じドメイン上の場所に配置することもできますが、サーバーサイドのプログラミングを完全に回避したいと考えています。幸いなことに、別の方法があります。

他の Google Data API と同様に、Google Base data API には、標準の XML に加えて、JSON 出力オプションがあります。先ほど確認したフィードは、JSON 形式で次のような URL 形式で出力されます。
http://www.google.com/base/feeds/snippets/-/products?bq=digital+camera&alt=json

JSON は軽量の交換形式で、階層的なネストやさまざまなデータ型が可能です。さらに重要な点として、JSON 出力はネイティブ JavaScript コードであるため、クロスドメイン制限を迂回し、スクリプトタグで参照するだけでウェブページに読み込めます。

Google Data API では、JSON が読み込まれた後に実行するコールバック関数で「json-in-script」出力を指定することもできます。スクリプトタグをページに動的に追加し、それぞれに異なるコールバック関数を指定できるため、JSON 出力がさらに扱いやすくなります。

したがって、Base API JSON フィードをページに動的に読み込むには、以下の関数を使用して、フィード URL(altcallback の値を追加)でスクリプトタグを作成し、ページに追加します。

function getJSON() {
  var script = document.createElement('script');

  var url = "http://www.google.com/base/feeds/snippets/-/products?bq=digital+camera";
  script.setAttribute('src', url + "&alt=json-in-script&callback=listResults");
  script.setAttribute('type', 'text/JavaScript');
  document.documentElement.firstChild.appendChild(script);
}

そのため、コールバック関数 listResults が、唯一のパラメータとして JSON で渡された JSON を反復処理し、箇条書きの項目ごとに各エントリの情報を表示できるようになりました。

  function listTasks(root) {
    var feed = root.feed;
    var html = [''];
    html.push('<ul>');
    for (var i = 0; i < feed.entry.length; ++i) {
      var entry = feed.entry[i];
      var title = entry.title.$t;
      var content = entry.content.$t;
      html.push('<li>', title, ' (', content, ')</li>');
    }
    html.push('</ul>');

    document.getElementById("agenda").innerHTML = html.join("");
  }

Google Gears を追加する

Google Data API を介して Google ベースと通信できるアプリケーションが作成されたので、このアプリケーションをオフライン実行できるようにしたいと思います。ここで、Google Gears の出番です。

オフライン可能なアプリケーションを作成する際は、さまざまなアーキテクチャを選択できます。アプリケーションの動作はオンラインかオフラインか、検索などの一部の機能が無効になっていますか?同期をどのように処理しますか。)

今回のケースでは、Gears を使用していないブラウザのユーザーにもアプリの使用を許可し、オフラインでの使用とよりレスポンシブな UI の利点をプラグインにもたらすことを目指しました。

アーキテクチャは次のようになります。

  • Google には、検索クエリの保存とそれらのクエリからの結果を返す JavaScript オブジェクトがあります。
  • Google Gears がインストールされている場合は、すべてがローカル データベースに保存される Gears バージョンを入手できます。
  • Google Gears がインストールされていない場合は、Cookie にクエリを保存し、クエリ結果を一切保存しないバージョン(応答性がやや低下)になります。結果が大きすぎて Cookie に保存できません。
このアーキテクチャの優れている点は、ショップ全体で if (online) {} のチェックを行う必要がないことです。代わりに、アプリケーションには Gears のチェックが 1 つ含まれ、正しいアダプタが使用されます。


Gears ローカル データベースを使用する

Gears のコンポーネントには、組み込みですぐに使用できるローカル SQLite データベースがあります。MySQL や Oracle などのサーバー側データベース向けの API を以前に使用したことがある場合、馴染みのあるシンプルなデータベース API が存在します。

ローカル データベースを使用する手順はごく簡単です。

  • Google Gears オブジェクトを初期化する
  • データベース ファクトリ オブジェクトを取得し、データベースを開きます。
  • SQL リクエストの実行を開始する

早速見ていきましょう。


Google Gears オブジェクトを初期化する

アプリケーションは /gears/samples/gears_init.js の内容を直接読み取るか、コードを独自の JavaScript ファイルに貼り付ける必要があります。<script src="..../gears_init.js" type="text/JavaScript"></script> を実行すると、google.gears 名前空間にアクセスできるようになります。


データベース ファクトリ オブジェクトを取得し、データベースを開く
var db = google.gears.factory.create('beta.database', '1.0');
db.open('testdb');

この 1 回の呼び出しで、データベース スキーマをオープンするためのデータベース オブジェクトが提供されます。データベースを開くと、それらは同一のオリジン ポリシー ルールを介してスコープ設定されるため、「testdb」が私の「testdb」と競合することはありません。


SQL リクエストの実行を開始する

これで、SQL リクエストをデータベースに送信する準備が整いました。「選択」リクエストを送信すると、結果セットが返され、目的のデータで反復処理できます。

var rs = db.execute('select * from foo where name = ?', [ name ]);

返された結果セットを操作するには、次のメソッドを使用します。

booleanisValidRow()
voidnext()
voidclose()
intfieldCount()
stringfieldName(int fieldIndex)
variantfield(int fieldIndex)
variantfieldByName(string fieldname)

詳細については、Database Module API のドキュメントをご覧ください。(編集者注: Google Gears API は利用できなくなりました)。


GearsDB を使用して低レベル API をカプセル化する

私たちは、一般的なデータベース タスクのカプセル化と、それをより便利なものにしたいと考えました。次に例を示します。

  • 私たちは、アプリケーションのデバッグ時に生成された SQL をうまくロギングする方法を探していました。
  • 例外処理を 1 か所で行うのではなく、1 か所で処理したいと考えました。try{}catch(){}
  • データを読み書きするときは、結果セットではなく JavaScript オブジェクトを処理したいと考えました。

これらの問題に対処するために、Google では Database オブジェクトをラップするオープンソース ライブラリ GearsDB を作成しました。ここでは、GearsDB の活用方法について説明します。

初期設定

window.onload コードで、依存するデータベース テーブルが設定されていることを確認する必要があります。次のコードが実行されたときにユーザーが Gears をインストールしている場合、GearsBaseContent オブジェクトを作成します。

content = hasGears() ? new GearsBaseContent() : new CookieBaseContent();

次に、データベースを開いてテーブルを作成します(テーブルが存在しない場合)。

db = new GearsDB('gears-base'); // db is defined as a global for reuse later!

if (db) {
  db.run('create table if not exists BaseQueries' +
         ' (Phrase varchar(255), Itemtype varchar(100))');
  db.run('create table if not exists BaseFeeds' + 
         ' (id varchar(255), JSON text)');
}

この時点で、クエリとフィードを格納するテーブルができました。コード new GearsDB(name) は、指定された名前のデータベースの開始をカプセル化します。run メソッドは低レベルの execute メソッドをラップしますが、コンソールへのデバッグ出力と例外のトラップも処理します。


検索語句を追加する

アプリを初めて実行する場合、検索履歴はありません。製品内で Nintendo Wii を検索しようとした場合、その語句は BaseQueries テーブルに保存されます。

Gears バージョンの addQuery メソッドでは、入力を取得して insertRow を介して保存します。

var searchterm = { Phrase: phrase, Itemtype: itemtype };
db.insertRow('BaseQueries', searchterm); 

insertRow は JavaScript オブジェクト(searchterm)を受け取り、テーブルへの INSERT を行います。また、制約(「複数のボブ」の一意性ブロックの挿入など)を定義することもできます。ただし、ほとんどの場合、これらの制約はデータベース自体で処理することになります。


すべての検索キーワードを取得する

過去の検索のリストを入力するには、selectAll という名前の適切なラッパーを使用します。

GearsBaseContent.prototype.getQueries = function() {
  return this.db.selectAll('select * from BaseQueries');
}

これにより、データベース内の行(例: [ { Phrase: 'Nintendo Wii', Itemtype: 'product' }, { ... }, ...])に一致する JavaScript オブジェクトの配列が返されます。

この場合、リスト全体を返すことができます。しかし、データが大量にある場合は、SELECT 呼び出しでコールバックを使用することをおすすめします。これにより、返される各行で入力を操作できるようになります。

 db.selectAll('select * from BaseQueries where Itemtype = ?', ['product'], function(row) {
  ... do something with this row ...
});

GearsDB のその他の役立つ select メソッドには、次のようなものがあります。

selectOne(sql, args)一致する最初の JavaScript オブジェクト(1 つまたは 1 つ)を返す
selectRow(table, where, args, select)通常、SQL を無視する単純なケースに使用される
selectRows(table, where, args, callback, select)selectRow と同じですが、複数の結果を持ちます。

フィードを読み込む

Google Base から結果フィードを取得したら、それをデータベースに保存する必要があります。

content.setFeed({ id: id, JSON: json.toJSONString() });

... which calls ...

GearsBaseContent.prototype.setFeed = function(feed) {
  this.db.forceRow('BaseFeeds', feed);
}

まず、JSON フィードを取得して、toJSONString メソッドを使用して文字列として返します。次に、feed オブジェクトを作成し、それを forceRow メソッドに渡します。forceRow は、エントリが存在しない場合に INSERT を挿入するか、既存のエントリを UPDATE します。


検索結果の表示

このアプリは、右側のパネルに特定の検索結果を表示します。検索キーワードに関連付けられたフィードを取得する手順は次のとおりです。

GearsBaseContent.prototype.getFeed = function(url) {
  var row = this.db.selectRow('BaseFeeds', 'id = ?', [ url ]);
  return row.JSON;
}

行の JSON を取得したら、eval() を使用してオブジェクトを取得できます。

eval("var json = " + jsonString + ";");

レースを終えて、JSON のインナー HTML コンテンツをページに掲載できるのです。


リソース ストアを使用してオフライン アクセスを行う

ローカル データベースからコンテンツを取得するため、このアプリはオフラインでも動作します。

不正解です。このアプリを開始するには、アプリの JavaScript、CSS、HTML、画像などのウェブリソースを読み込む必要があります。現状では、ユーザーが以下の手順を踏んでも、アプリは引き続き動作する可能性があります。オンラインを開始する、検索を行う、ブラウザを閉じないオフラインにするといった方法があります。この動作は、アイテムがブラウザのキャッシュに残っていることがあるためです。そうでない場合はどうなるでしょうか。Google は、再起動後など、ユーザーがゼロからアプリにアクセスできるようにしたいと考えています。

これを行うには、LocalServer コンポーネントを使用してリソースをキャプチャします。リソース(アプリケーションの実行に必要な HTML や JavaScript など)がキャプチャされると、Gears はこれらのアイテムを保存し、ブラウザからのリクエストをトラップして返します。ローカル サーバーはトラフィック コップとして機能し、保存されたコンテンツを店舗から返します。

また、ResourceStore コンポーネントを利用します。これを使用すると、キャプチャするファイルをシステムに手動で指示する必要があります。多くのシナリオでは、トランザクション方式でアプリケーションをバージョニングしてアップグレードできるようにします。一連のリソースをまとめてバージョンを定義し、新しいリソースをリリースするときにユーザーがファイルをシームレスにアップグレードできるようにする必要があります。それがモデルの場合は、ManagedResourceStore API を使用します。

リソースをキャプチャするために、GearsBaseContent オブジェクトは次のことを行います。

  1. キャプチャが必要なファイルの配列を設定する
  2. LocalServer を作成する
  3. 新しい ResourceStore を開くか作成する
  4. 店舗にページを取り出すよう呼びかける
// Step 1
this.storeName = 'gears-base';
this.pageFiles = [
  location.pathname,
  'gears_base.js',
  '../scripts/gears_db.js',
  '../scripts/firebug/firebug.js',
  '../scripts/firebug/firebug.html',
  '../scripts/firebug/firebug.css',
  '../scripts/json_util.js',    'style.css',
  'capture.gif' ];

// Step 2
try {
  this.localServer = google.gears.factory.create('beta.localserver', '1.0');
} catch (e) {
  alert('Could not create local server: ' + e.message);
  return;
}

// Step 3
this.store = this.localServer.openStore(this.storeName) || this.localServer.createStore(this.storeName);

// Step 4
this.capturePageFiles();

... which calls ...

GearsBaseContent.prototype.capturePageFiles = function() {
  this.store.capture(this.pageFiles, function(url, success, captureId) {
    console.log(url + ' capture ' + (success ? 'succeeded' : 'failed'));
  });
}

ここで注意すべき点は、リソースをキャプチャできるのは自社ドメイン上にのみです。SVN トランクにある元の「gears_db.js」ファイルから GearsDB JavaScript ファイルに直接アクセスしようとすると、この制限に達しました。解決方法は簡単です。外部リソースをダウンロードして、ドメインに配置する必要があります。302 または 301 リダイレクトは機能しません。LocalServer は、200(成功)または 304(変更なし)のサーバーコードのみを受け入れるからです。

これは影響を受けます。画像が images.yourdomain.com に配置されている場合、キャプチャすることはできません。www1 と www2 は互いに認識できません。サーバーサイド プロキシを設定することもできますが、その場合、アプリケーションを複数のドメインに分割する目的が果たされません。

オフライン アプリケーションのデバッグ

オフライン アプリケーションのデバッグはもう少し複雑です。テストのシナリオが増えました。

  • 完全にキャッシュされたアプリを使用してオンラインになっている
  • オンラインだが、アプリにまだアクセスしておらず、キャッシュに何も保存していない
  • オフラインだがアプリにアクセスした
  • オフラインになっても、アプリにはアクセスしたことがありません(利用するのは適切ではありません)。

わかりやすくするために、次のパターンを使用しました。

  • Firefox(または任意のブラウザ)では、単にキャッシュからデータが取り出されないようにするため、Google はキャッシュを無効にします。
  • Firebug(および他のブラウザでテストするには Firebug Lite)を使用してデバッグします。場所を問わず console.log() を使用し、念のため、コンソールで検出します。
  • 次の場所にヘルパー JavaScript コードを追加します。
    • データベースを消去し、すべてのステータスを取得
    • キャプチャしたファイルを削除するため、再読み込みすると再びインターネットに取得されます(開発の反復処理を行う場合に便利です)。

デバッグ ウィジェットは、Gears がインストールされている場合のみページの左側に表示されます。クリーンアップ用のコードには、次のコールアウトがあります。

GearsBaseContent.prototype.clearServer = function() {
  if (this.localServer.openStore(this.storeName)) {
    this.localServer.removeStore(this.storeName);
    this.store = null;
  }
}

GearsBaseContent.prototype.clearTables = function() {
  if (this.db) {
    this.db.run('delete from BaseQueries');
    this.db.run('delete from BaseFeeds');
  }
  displayQueries();
}

まとめ

Google Gears の操作はとてもシンプルです。また、GearsDB を使用してデータベース コンポーネントをさらに簡単にし、手動の ResourceStore を使用しましたが、この例で十分に問題ありません。

最も時間を費やしている領域は、データをオンラインにする際のタイミングと、オフラインでの保存方法に関する戦略を定義しています。データベース スキーマの定義に時間をかけることが重要です。後でスキーマを変更する必要が生じた場合は、既存のユーザーにデータベースのバージョンがすでにあるので、その変更を管理する必要があります。つまり、DB アップグレードがあれば、スクリプト コードを配布する必要があります。この問題の最小化は当然のことです。リビジョンの管理に役立つ小さなライブラリである GearShift を試してみることをおすすめします。

ManagedResourceStore を使用してファイルを追跡することもあるため、次のような結果になります。

  • 良質なユーザーになって、今後のクリーン アップグレードを可能にするために、ファイルをバージョニングします。
  • ManagedResourceStore の機能として、URL に別のコンテンツ エイリアスを設定できます。有効なアーキテクチャの選択は、Gears 以外のバージョンとして Gears 以外のバージョンを使用することです。その場合、Gears 自体は Gears_base_withgears.js をダウンロードします。これにはすべてのオフライン サポートがあります。
このアプリでは、インターフェースを 1 つだけ使用し、そのインターフェースを 2 つの方法で簡単に実装できました。

アプリケーションをギアアップする作業が楽しく簡単になったはずです。ご不明な点や共有すべきアプリがある場合は、Google Gears フォーラムにご参加ください。