まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

タグ:pouchdb

 は IBM CloudantApache CouchDB と API 互換のある NoSQL データベースです。JavaScript で操作することができます。npm を使ってサーバーサイドで動かすこともできますが、ブラウザから JavaScript ライブラリをロードして、個々のブラウザ内で使うことも可能です。

特にこれをブラウザから使う場合、マスターデータはクラウドの IBM Cloudant で保持しつつ、必要なデータをユーザーのブラウザと同期して、ほぼすべての処理をローカルブラウザ内で完結させる(=時間のかかるDBアクセスをローカルDBだけを対象に行えばよくなるので、アプリケーションとしての安定性やパフォーマンス向上も期待できる)、ということが可能になります。とても強力な同期機能を持ったデータベースエンジンと言えます。
2018040900


この「同期」を具体的にどうやるか、という内容が今回のブログエントリです。


今回の説明では、サーバー側の DB を IBM Cloudant で用意することにします。IBM Cloud のライトアカウントを作成すると容量 1GB まで使えるライトプランの DBaaS が無料で用意できます。

で、この Cloudant DB をブラウザに同期・・・するわけですが、IBM Cloudant を使う場合はその前に1つ設定が必要です。標準状態の IBM Cloudant はクロスオリジンからのアクセスを許可していません。そのため、標準設定のままウェブブラウザから同期をとろうとするとクロスオリジンアクセスになってしまい、エラーとなってしまいます。したがってクロスオリジンアクセスを許可するよう、設定を変更する必要があります。詳しくはこちらにも記載していますが、curl コマンドと IBM Cloudant の API を使って IBM Cloudant の CORS アクセスを有効にするための設定を行います:
$ curl -i -u 'db_username:db_password' -XPUT 'https://db_username.cloudant.com/_api/v2/user/config/cors' -H 'Content-type: application/json' -d '{"enable_cors":true,"allow_credentials":true,"origins":["*"]}'

↑db_username, db_password は IBM Cloudant のインスタンスにアクセスするためのユーザー名およびパスワードです。


次にブラウザ内の JavaScript で DB の同期を行います。普通に「データベースそのものの同期をとる」のであれば話は単純で、以下のようなコードを記述するだけです:
  :
  :
<html>
<head>
<script src="https://cdn.jsdelivr.net/pouchdb/5.4.5/pouchdb.min.js"></script>
<script>
var localDB = new PouchDB( 'testdb' );
var remoteDB = new PouchDB( 'https://db_username:db_password@db_username.cloudant.com/testdb' );

remoteDB.sync( localDB, {
live: true, retry: true });
: :

まず CDN を指定して PouchDB のライブラリをロードします。そして IBM Cloudant のユーザー名(db_username)とパスワード(db_password)を指定して、'testdb' という名前のデータベースインスタンスを remoteDB 変数に代入します。これで IBM Cloudant 上のリモートデータベースを remoteDB から操作できるようになりました。同時にローカルブラウザ内に(同じ名前の)'testdb' という名前のデータベースインスタンスを作って localDB 変数に代入しています(こちらは作成時点では空です)。 この2つのリモート/ローカルデータベースを sync() 関数で同期するように指定しています。これによってこれら2つのデータベースインスタンス変数の内容は自動同期され、一方(例えばブラウザ内の localDB)に変化が起こるともう一方(サーバーの remoteDB)にその変化内容が勝手に反映される、という仕組みが実現できます。

ちなみに sync() 実行時の live:true オプションはリアルタイム同期の指定で、retry:true オプションは一度接続が切れた後に自動的にリトライして接続が戻った時に同期も復活させるための指定です。ここまでは超簡単です。


さて、本来やりたかったのはデータベースをまるごと同期するのではなく、データベースの一部だけを同期する、というものです。上記例だと remoteDB はサーバー側のものなので、全部で数ギガバイトになったりそれ以上になったりすることも想定しないといけないのですが、localDB はブラウザ内で作るものなのであまり大きくなっては困ります。そこで(一般的には部分同期とか Partial Sync とか呼ばれる方法で)特定条件を満たす一部の文書だけを対象にローカルに同期し、ローカルでの変更・追加・削除といった処理をサーバー側のマスターに同期し直す方法を紹介します。

PouchDB にも部分同期機能は存在しています。ただそこで「この条件を満たす文書だけ」を指定する方法がかなり限られていて、現時点では文書 ID を配列で指定する方法しかないように見えます(このフィールドがこの値で・・・みたいなクエリーではできないっぽい)。具体的にはこんな感じ:
  :
  :
<html>
<head>
<script src="https://cdn.jsdelivr.net/pouchdb/5.4.5/pouchdb.min.js"></script>
<script>
var localDB = new PouchDB( 'testdb' );
var remoteDB = new PouchDB( 'https://db_username:db_password@db_username.cloudant.com/testdb' );

var doc_ids = [ 'xxx', 'yyy', 'zzz' ];  //. 同期対象文書の _id 値配列

remoteDB.sync( localDB, {
doc_ids: doc_ids,
live: true, retry: true }); : :

上記例では 'xxx', 'yyy', 'zzz' という3つの文書ID(Cloudant 内だと文書の _id の値)を指定して、sync() 関数を doc_ids パラメータで配列指定しています。これだけで localDB には指定された3文書だけが同期され、削除を含めた変更があるとリモートにも即時に反映されるようになります。

したがって実装する場合はページロード時の最初に IBM Cloudant に対してクエリー API を実行して同期の対象となる文書(の ID 配列)をまとめて取得し、その ID 配列を指定して PouchDB と部分同期する、という流れになると思っています。この方法なら最初のロード時のネットワーク接続は必須になりますが、どのみちページをロードするにもネットワークは必須だし、同期をとった後はネットワークが切れても平気、というシステム構成が可能になります。


なお、一点だけ注意が必要なことがあります。この方法で部分同期した後に localDB に新たに文書を追加した場合です。部分同期の条件は doc_ids に指定された文書ID配列だったので、ここに含まれない新しい文書を追加してもサーバー側には同期されません。その場合は新たに doc_ids を指定しなおして(新しい文書の ID を追加して)改めて sync() 関数を実行する必要があります:
  :
  :
<html>
<head>
<script src="https://cdn.jsdelivr.net/pouchdb/5.4.5/pouchdb.min.js"></script>
<script>
var localDB = new PouchDB( 'testdb' );
var remoteDB = new PouchDB( 'https://db_username:db_password@db_username.cloudant.com/testdb' );

var doc_ids = [ 'xxx', 'yyy', 'zzz' ];

remoteDB.sync( localDB, {
doc_ids: doc_ids, live: true, retry: true }); : : function add( doc ){ //. ドキュメントを追加する処理 localDB.put( doc ).then( function( res ){ //. localDB に文書(doc)追加 doc_ids.push( doc._id ); //. 文書の _id 値を配列に追加 remoteDB.sync( localDB, { //. sync() 再実行 doc_ids: doc_ids, live: true, retry: true }); }).catch( function( err ){ console.log( err ); }); }

例えば上記例では add( doc ) 関数の中で doc に指定されたオブジェクトを localDB に追加する想定で処理を記述していますが、localDB.put() が成功したら doc_ids 配列を更新した上で remoteDB.sync() を再実行して同期条件を変えるようにしています。

現実問題としては追加なのか更新なのか(どちらも put() 関数を使う)、更新だとすると _rev の値も必要になって・・・とか、多少細かい実装が必要になることも事実ですが、一応これだけでローカルDBとその部分同期を使ったアプリの実装はできることになります。

問題は上述したローカルDBに同期したい文書の ID をどうやって調べるかですが、そこはやっぱり一度サーバーにクエリー投げるしかないのかなあ・・・


なお、PouchDB の API Reference はこちらを参照ください:
https://pouchdb.com/api.html


IBM Cloud(Bluemix) から提供されている NoSQL データベースの DBaaS である Cloudant は読み書きのための REST API が公開されています。各種プログラミング言語から HTTP ベースの API を実行してデータを読み書きすることが可能です。

ただ、これらの API には一般的な CORS(Cross-Origin Resource Sharing) の制限がかかっており、ウェブブラウザの JavaScript からは読み書きができないように設定されています。この CORS 制限を無効にする方法が分かったので、ブログで紹介する形で手順等を紹介します。

まず今回ブラウザからアクセスする対象とするデータベースをこちらとします。pouchdb というデータベースで、現在4件のドキュメントが登録されています:
2018040900


このデータベースに簡単にアクセスするため、今回は PouchDB ライブラリを使うことにします。PouchDB は軽量かつ CouchDB(Cloudant) 互換のデータベースです。この PouchDB を CDN(//cdn.jsdelivr.net/pouchdb/5.4.5/pouchdb.min.js) からロードして、データベースオブジェクトを作り、その中の全文書を取り出して表示する、という処理を実装すると↓のような感じになります:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>CORS check</title>
<script src="//cdn.jsdelivr.net/pouchdb/5.4.5/pouchdb.min.js"></script>
<script>
var cloudant_db_url = 'https://USERNAME:PASSWORD@USERNAME.cloudant.com/pouchdb';

var db = new PouchDB( cloudant_db_url );

db.allDocs( { include_docs: true } ).then( function( docs ){
  console.log( docs );
}).catch( function( err ){
  console.log( 'error' );
});
</script>
</head>
<body>
</body>
</html>

主な処理内容を簡単に解説します。まず CDN から PouchDB ライブラリをロードし、Cloudant のデータベース URL を指定して、データベースオブジェクトを作ります(上記の USERNAME は Cloudant のユーザー名、PASSWORD は同パスワードです)。そして allDocs() メソッドで全文書を取り出して結果を console.log() でコンソールに表示する、という内容の JavaScript を含む HTML になっています。


この HTML を HTTP サーバー上に配置してウェブブラウザからアクセスします。取得結果はコンソールに表示されるので、あらかじめコンソール画面を表示(FireFox であれば F12 キー)しておきます。その状態でブラウザから同ページにアクセスすると・・・コンソールには「クロスオリジン要求・・」というエラーが表示されます。これはつまり Cloudant 側でクロスオリジンからのアクセスを許可していないため、アクセスは拒絶され、そのエラーが表示されています。これが Cloudant のデフォルトでの挙動です:
2018040901


では Cloudant の CORS アクセス(クロスオリジンからのアクセス)を有効にしてみます。curl コマンドの使えるターミナルから、以下のコマンドを実行します:
$ curl -i -u 'USERNAME:PASSWORD' -XPUT 'https://USERNAME.cloudant.com/_api/v2/user/config/cors' -H 'Content-type: application/json' -d '{"enable_cors":true,"allow_credentials":true,"origins":["*"]}'

このコマンドでは認証用の ID とパスワード、HTTP ヘッダの Content-Type: application/json を指定し、/_api/v2/user/config/cors パスに対して、CORS アクセスを有効にするようデータを POST して実行しています:

2018040902


上記のように {"ok": true} という結果が返ってくればコマンドは成功し、クロスオリジンからのアクセスも許可されています。試しに再度同じ HTML ページを(リロードするなどして)表示すると、今度は allDocs() メソッドが成功し、期待通りに(4件の)データを取得し、コンソールに表示できているはずです:
2018040903


これで Cloudant の API をブラウザ(の JavaScript )からも直接実行する術が確保できました。これでウェブブラウザの HTML から直接 Cloudant を操作したり、ウェブブラウザ内の PouchDB と連携することもできるようになります。



(参考)
How to use CORS with a Cloudant account

CORS


JavaScript で Cloudant や CouchDB を操作できるようになるライブラリの1つに PouchDB があります:
https://pouchdb.com/

2016031701


PouchDB は CouchDB および CouchDB と互換性のあるローカル NoSQL データベースです。JavaScript を使ってデータを読み書きし、またリモートの CouchDB サーバーにアクセスしたり同期したりすることもできるものです。広い意味ではデータベース本体および JavaScript ライブラリを合わせて PouchDB と呼ぶこともあります。

という特徴をもった、この PouchDB の JavaScript ライブラリを使うと、リモートの CouchDB データベースにアクセスすることができるようになります。JavaScript ライブラリなのでクロスサイトスクリプティング等のセキュリティ制約を意識しながら使う必要があります。

今回、紹介するのはこんな環境で JavaScript を使ってデータベースにアクセスする、というものです。HTML ファイルがローカルディスク内にあるので、普通にアクセスするとクロスサイトスクリプティング制約にかかって使えませんが、その制約を回避する方法と合わせて紹介します:
2016031705


まずは操作先の CouchDB サーバー環境を用意します。外部からアクセスするので Access-Control-Allow-Origin ヘッダのカスタマイズも必要になります。この辺りの手順は以下2つのブログエントリを参照ください:


この CouchDB サーバーの IP アドレスは XX.XX.XX.XX、ポート番号はデフォルトの 5984 であるとして以下を記述します。まずは CouchDB サーバーの管理コンソールにアクセスして、この後リモートから操作するためのデータベースを1つ作成しておきましょう。ウェブブラウザで http://XX.XX.XX.XX:5984/_utils/ にアクセスして管理コンソール画面にアクセスし、"Create Database" と書かれた箇所をクリックします:
2016031701


作成するデータベースの名前を問われるので適当な名称(この例では mydb)を入力して "Create" ボタンをクリックします:
2016031702


指定した名称のデータベースが作成され、その内容が表示されます。この時点では作成直後なのでドキュメントは存在していないはずです。とりあえず元の画面に戻るために "Overview" と書かれた箇所をクリックします:
2016031703


もとの管理コンソールのデフォルト画面に戻ります。先程まではなかった mydb というデータベースが追加されていることがわかります。このデータベースにリモートからアクセスすることが今回の目的です:
2016031704


次にローカル側の準備です。まずなにはともあれ PouchDB ライブラリが必要なので、PouchDB のトップページから最新版の PouchDB をダウンロードします。この記事を書いている時点での最新バージョンは 5.3.0 で、ダウンロードファイル名は pouchdb-5.3.0.min.js でした:
2016031701


次にダウンロードした PouchDB ライブラリを使って、リモートの CouchDB サーバーのデータベースにアクセスするプログラムをローカルに作成します。具体的には以下の様な HTML ファイル(index.html)をダウンロードした pouchdb-5.3.0.min.js と同じディレクトリに作成します:
<html>
<head>
<script src="./pouchdb-5.3.0.min.js"></script>
<script>
var db = new PouchDB('http://XX.XX.XX.XX:5984/mydb');
db.info().then( function( info ){
  console.log( info );
});
</script>
</head>
<body>
</body>
</html>

ソース内の XX.XX.XX.XX は CouchDB サーバーのホスト名または IP アドレスです。また mydb はアクセス先のデータベース名称です。この HTML では同じディレクトリにある pouchdb-5.3.0.min.js を読み込み、http://XX.XX.XX.XX:5984/mydb にアクセスして、成功したらその情報をウェブコンソールに表示する、というものです。繰り返しになりますが、このソースファイルはローカル環境にあり、このようなファイル構成になっています。そして HTML ファイルの読み込みと同時にリモートの CouchDB サーバーにアクセスして mydb データベースの情報を表示する、という内容の処理が記述されています:
2016031706


ではこの index.html をダブルクリックしてデフォルトブラウザで開きます。注意点としてウェブサーバー上の HTML ファイルを開いているのではなく、ローカルディスク内の HTML ファイルを開いている(アドレスのプロトコル部分が file:/// で始まっていることに注目)ことを確認してください。そして F12 を押してウェブコンソールを表示すると、上記 HTML 内の JavaScript コードが実行されて XX.XX.XX.XX:5984 の mydb データベースに関する情報が出力されていることを確認してください。ローカル環境から JavaScript でリモート環境の CouchDB サーバーのデータベースの情報を取得することができました!
2016031707


もしもここで以下のようなメッセージが表示されてしまう場合は、クロスサイトスクリプティングの制約にかかって、JavaScript が実行できなくなっている可能性が高いです。その場合はこのページの上部にある「CouchDB の Access-Control-Allow-Origin ヘッダを設定する」のリンク先に書かれている設定が足りない(または間違っている)ので、この内容を確認した上で再度試してみてください:
2016031708


以前のブログエントリで CouchDB サーバーの導入手順(というかビルド手順)を紹介しました:
CentOS に CouchDB をインストールする


上記の方法で自由に使うことのできる CouchDB サーバー1ノードが手元の環境に出来上がります。が、PouchDB などの JavaScript ライブラリを使ってこのサーバー上の DB を操作しようとすると、まだ手順が足りないこともあります。

具体的には、素のままの設定状態で JavaScript を使って CouchDB の REST API を実行しようとすると、クロスサイトスクリプティングのセキュリティ制約にかかってしまい、実行時にエラーを起こしてしまうのです。

クロスサイトスクリプティング制約を回避するには、(CouchDB の)HTTP サーバーに Access-Control-Allow-Origin ヘッダを付与して、明示的に制約を回避できるアクセス元を指定して回避する必要があります。その方法を紹介します。


まずは上記サイトを参照して CouchDB サーバー環境を用意します。以下の手順では上記方法で導入した CouchDB サーバー環境を利用するものとします。

次に /usr/local/etc/couchdb/local.ini をテキストエディタで開き、以下の内容を加えます(この例では Access-Control-Allow-Origin に * を設定して、事実上アクセス元の制約なしにアクセスできるような設定にしています):
  :
  :
[httpd]
port = 5984
bind_address = 0.0.0.0
enable_cors = true

[cors]
credentials = true
origins = *

  :
  :

この状態で CouchDB を再起動すると、CouchDB の REST API はどのサーバーからでもアクセスできるようになります。
# /etc/init.d/couchdb restart


実際に PouchDB を使って外部からのアクセスが可能であることを示すコードは別のエントリで用意する予定です。

このページのトップヘ