このページの本文へ

NoSQLってどう使うの? CouchDBでポケモンGOレコーダーを作ってみた

2016年09月10日 01時00分更新

文●Wern Ancheta

  • この記事をはてなブックマークに追加
本文印刷
NoSQLで何か作ってみたいんだよね…という人へ、CouchDBの使い方を2回に分けて紹介します。サンプルは、ポケモンの出現場所を記録するWebアプリ。休日のお楽しみに、どうぞ。

1471509500pokespawn-screen本記事では、ApacheのNoSQLデータベースCouchDBを使い方を紹介します。実用的な面に注目し、CouchDBのHTTP APIを経由したデータベース運用の実行方法と、PHPでの動作方法、今後の基礎づくり、そしてより複雑な投稿に焦点をあてます。インストール方法やなぜ使うのかなどは説明しません。

マシン上にはすでにCouchDBとFuton(CouchDBのWebベース管理コンソール)が設定されていると仮定します。同じ方法でセットアップしたい場合はHI boxの使用をお勧めします。

1470860091couch

注意:分かりやすくするため、ここではLocalhostでローカルマシンを参照しますが、すべて設定済みのVMを使用する場合は、転送されたポートとともに、設定されているカスタムバーチャルホストとローカルドメインがあります。改良済みHomesteadでは、バーチャルボックスをプロビジョニングする前にHomestead.yaml構成ファイルに転送したいポートを一覧表示するだけです。

注:簡単にするために、私たちが私たちのローカルマシンを参照してくださいよ 、ここが、あなたは完全に構成されたVMを使用している場合は、おそらくカスタムのバーチャルホストとローカルドメインが転送されたポートと一緒に、セットアップされています。

データベースの作成

新しくCouchDBデータベースを作成するには、http://localhost:5984/_utils/のFutonを参照してください。以下のインターフェイスが表示されます。

futon

データベース作成をクリックしてデータベース名を入力し、作成をクリックしてください。

作成されると次の画面が表示されます。

couchdb database

新規ドキュメント作成のオプションのみがあると分かります。CouchDBにおけるドキュメントは、リレーショナルデータベースにおけるテーブルの行に相当します。では、テーブルの作成方法はどのようにすればよいのでしょうか。

MongoDBのようなNoSQLデータベースを使用している場合、まず知っておかなければならないのは、CouchDBにはコレクションやテーブルのようなものがないことです。あるのはドキュメントのみです。ただし、データベースごとに1種類のデータしか保存できないという意味ではありません。CouchDBに作成した各ドキュメントがテーブルに属していなければ、データの種類ごとに異なる構造にできます。たとえばユーザーデータを保存する場合は次のような構造にできます。

{
    "id": 123,
    "fname": "doppo",
    "lname": "kunikida",
    "pw": "secret",
    "hobbies": ["reading", "sleeping"]
}

一方、ブログ記事の情報を保存したい場合は、次のような構造にできます。

{
    "title": "The big brown fox",
    "author": "fox",
    "text": "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Earum, quasi in id voluptates. Natus qui iure corporis ea voluptatem eius, possimus optio modi facere blanditiis quo, sequi suscipit eos nostrum.",
    "publish_date": "2016-07-07"
}

特定の種類のドキュメント(ユーザーやブログ投稿などの)を照会しやすくするには、特定の種類のドキュメントを保存するためのフィールドを追加できます。

{
    "id": "123",
    "fname": "doppo",
    "lname": "kunikida",
    "pw": "secret",
    "hobbies": ["reading", "sleeping"],
    "type": "users"
}

typeは特殊な種類のフィールドではないことに注意してください。これはただ便宜上使用されているだけです。

HTTP APIに伝える

CouchDBはHTTP APIを公開しているので、データベース作成にはcurlも使用できます。

curl -X PUT http://localhost:5984/<database name>

上のコマンドを実行すると、次のように返されます。

{"ok":true}

CouchDBは、レスポンスとしてJSON文字列を返します。ブラウザーとサーバーサイド面の両方での使用がとても簡単になります。

この記事では、CouchDBのHTTP APIと通信しやすいPostmanをお勧めします。はじめてPostmanを使用する場合は、『API Building and Testing Made Easier with Postman』を参照してください。

新規ドキュメントの作成

新しいドキュメントを作成するには、次のように、作成したデータベースへのPOSTリクエストを送信します。

http://localhost:5984/test_couch

CouchDBにリクエストを送信するときは、次のことを必ず覚えておいてください。

  • POSTPUTDELETEを介してデータを渡すときは、ヘッダーにapplication/jsonContent-Typeを指定します
  • ダブルクオート内で文字列をラップします

新規ドキュメントを作成するためのリクエストの例は次のとおりです。

Creating a new document with Postman

一括挿入

1つのリクエストで複数行のデータを挿入するには以下のようにします。

Bulk inserting via Postman

あとでドキュメントの読み出しを試すときも、このデータを使用します。例に沿って実行したい場合はcouchdb-bulk.jsonを使用してください。

ドキュメントを読み出す

現在データベースに格納されているすべてのドキュメントを取得します。

Retrieving all documents

CouchDBは、デフォルトでは最新のリビジョン番号(文書の特定のバージョンを表す一意の文字列)を含むオブジェクトのユニークID、キー(ユニークIDと同じ)、値のみを返します。リビジョンの詳細はあとで説明します。

保存したデータを読み出すには、include_docsをクエリパラメータとして指定しtrueに設定します。

http://localhost:5984/test_couch/_all_docs?include_docs=true

特定のドキュメントを読み出す

CouchDBではドキュメントIDを使って特定のドキュメントを読み出せます。

http://localhost:5984/test_couch/8939b0d23a0ba7a5ed55fd981d0010a0?include_docs=true

また、クエリパラメーターrevを介して読み出し番号を指定し、ドキュメントの特定のバージョンも読み出せます。

http://localhost:5984/test_couch/8939b0d23a0ba7a5ed55fd981d0010a0?rev=1-1841dec358ff29eca8c42a52f1c2a1d0&include_docs=true

新しくドキュメントを作成したり既存のドキュメントを更新するたびに、CouchDBは一意のリビジョン番号を生成し、ドキュメントのステートに割り当てます。たとえばskillと呼ばれる新規フィールドを追加してドキュメントを保存する場合、CouchDBはskillフィールドが追加される直前のドキュメントのコピーを保存します。ドキュメントの変更(たとえば特定のフィールドの値の更新、フィールドの削除、フィールド名の変更、新しいフィールドの追加など)をするたびに保存されるので過去のデータを保持したい場合にとても便利です。

Futonの特定のドキュメントにアクセスする場合は、その前のバージョンを検索できます。

Accessing revisions

ビュー

ビューでは、データベースから特定のデータを抽出し、特定の方法で並べられます。

ビューを作成するには、Futon上のデータベースにアクセスし、右上のドロップダウンのtemporary viewを選択します。これで次のインタフェースが表示されます。

create temporary view

残りの記事では以前に挿入したデータを使用します。

最初はトレーナーごとにポケモンをフィルタリングする機能を見てみます。

function(doc) {
  emit(doc.trainer, doc.name);
}

この関数をmap関数の値として追加します。この関数は、キーと値の2つの引数を受け取る、組み込みのemitメソッドを使用しています。キーはドキュメントのフィルタリングに使用されます。値は行ごとに返したい値のことです。

関数を実行した結果は次のとおりです。

view response

このとおり、すべてを返します。これはフィルタとして使用する値を指定していないためです。実際にこの関数の動きを見るには、save asボタンをクリックしてビューを保存します。

save view

デザインドキュメントとビューの名前を尋ねられます。デザインドキュメントは関連するビューのコレクションと考えられます。主にポケモンのデータを扱うドキュメントなので、pokemonと名づけます。ビューに関しては、filter_by_trainerのようにアクションの名前をつけます。

それでは、いま作成したビューにリクエストをしてみます。

http://localhost:5984/test_couch/_design/pokemon/_view/filter_by_trainer?key="Ash"

次の結果を返します。

filter by trainer results

クエリパラメーターにどんな値を渡しても、結果のフィルタリングにはkeyが使用されます。

配列フィールドでのフィルタリング

typeフィールドなどの配列フィールドでフィルタリングする必要があると仮定します。

すべての配列項目をループ処理し、ループ内からドキュメントをエミットする必要があるので、次のようにします。

function(doc) {
  for(var x = 0; x < doc.type.length; x++){
     emit(doc.type[x], doc.name); 
  }
}

このビューをfilter_by_typeとして保存し、次のURLにGETリクエストを送ります。

http://localhost:5984/test_couch/_design/pokemon/_view/filter_by_type?key="Fire"

いくつか種類のある中から「火」を持つすべてのポケモンを返します。

filter by type response

結果の並べ替えと制限

結果を並べ替えるには、並べ替えたいフィールドをエミットします。この場合、ポケモンが捕獲された日付で並べ替えができるようにownedフィールドをキーとしてエミットします。

function(doc){
   emit(doc.owned, doc.name);  
}

このビューをorder_by_ownedとして保存し、次のURLにリクエストを送ります。

http://localhost:5984/test_couch/_design/pokemon/_view/sort_by_owned

デフォルトではCouchDBはドキュメントを昇順で返すので、最も長く所有されているポケモンが最初にきます。ドキュメントを降順で並び替えるには、クエリパラメーターとしてdescending=trueを指定します。

http://localhost:5984/test_couch/_design/pokemon/_view/sort_by_owned?descending=true

返すドキュメントの数を制限するには、limitを返したいドキュメントの数で設定します。

http://localhost:5984/test_couch/_design/pokemon/_view/sort_by_owned?limit=5

結果のグループ化

各トレーナーが持つポケモンの数を返したい場合はreduce関数を使用します。reduce関数は、map関数が返す結果でグループ化と集計操作を実行できます。CouchDBには_count_sum_statsの3つの組み込みreduce関数があります。このセクションでは_count関数について取り上げます。

各トレーナーが持つポケモンの数を得るために_count関数を使います。次のmap関数の追加から始めます。

function(doc) {
   emit(doc.trainer, doc.name); 
}

reduce関数は_countに加えます。そしてビューの名前をgroup_by_trainerにして保存します。

次のURLにリクエストします。

http://localhost:5984/test_couch/_design/pokemon/_view/group_by_trainer?group=true

デフォルトでreduce関数から次のような結果を得るには、 group=trueに設定する必要があります。

{
  "rows": [
    {
      "key": null,
      "value": 9
    }
  ]
}

上の関数は、現在データベースにあるドキュメントの総数のみを結果として表示します。これは、reduce関数がmap関数の返す結果セット全体を単一のグループと見なしたことを意味します。

group=trueを設定すると、ドキュメントを特定のキー(doc.trainer)でグループ化するようCouchDBに伝え、以下の結果を返します。

{
  "rows": [
    {
      "key": "Ash",
      "value": 5
    },
    {
      "key": "Brock",
      "value": 1
    },
    {
      "key": "Misty",
      "value": 2
    },
    {
      "key": "Team Rocket",
      "value": 1
    }
  ]
}

ドキュメントの更新

ドキュメントを更新するには、特定のドキュメントの検索に使用したものと同じURLにPUTリクエストを送信し、更新したデータと一緒に最新のリビジョン番号へ渡します。

update document

上のスクリーンショットから、CouchDBは特定のフィールドの更新をサポートしないことが分かります。既存のデータをフェッチし、更新し、そのあとそのデータをデータベースに戻す必要があります。

ドキュメントの削除

ドキュメントを削除するには、以下のURLへDELETEリクエストを実行します。

http://localhost:5984/test_couch/<Document ID>?rev=<Revision ID>

これはドキュメントを取得するURLと同じフォーマットで、リビジョンIDを渡しているのでドキュメントの特定のリビジョンも削除できます(関数を元に戻しますか?)。

PHPを使用した作業

PHPでCouchDBの作業をする2つの方法があります。DoctrineのCouchDB Clientのように、Guzzleを使う、もう1つはCouchDBで作業するために特別に作成したライブラリーを使います。このセクションでは各ライブラリで作業する方法を取り上げます。

Guzzle

データを取得する場合はGETリクエストを使用し、オプションを指定するためqueryに渡します。

<?php
require 'vendor/autoload.php';

use GuzzleHttp\Client;

$client = new GuzzleHttp\Client(['base_uri' => 'http://localhost:5984']);

$response = $client->request('GET', '/test_couch/_all_docs', [
    'query' => [
        'include_docs' => 'true'
    ]
]);

$json = $response->getBody()->getContents();
$data = json_decode($json, true);

次に一括挿入します。

$docs = [
    'docs' => [
        [
            "name" => "Tangela",
            "type" => ["Grass"],
            "trainer" => "Erika",
            "gender" => "f",
            "owned" => "1999-07-27"
        ],
        [
            "name" => "Wobbuffet",
            "type" => ["Psychic"],
            "trainer" => "Elesa",
            "gender" => "m",
            "owned" => "2014-09-09"
        ],
        [
            "name" => "Gogoat",
            "type" => ["Grass"],
            "trainer" => "Ramos",
            "gender" => "m",
            "owned" => "2015-10-17"
        ]
    ]
];

$client->request('POST', '/test_couch/_bulk_docs', [
    'headers' => [
        'Content-Type' => 'application/json'
    ],
    'body' => json_encode($docs)
]);

上記のコードから、同じルールが変わらず適用されていることがわかります。

ドキュメントを更新するには、PUTリクエストを使用し、データベース名の直後にパスとしてドキュメントIDを渡します。そしてbodyで変更されたドキュメントに渡します。

$doc = [
    '_rev' => '2-ff235602e45c46aed0f8834c32817546',
    'name' => 'Blastoise',
    'type' => ['Water'],
    'gender' => 'm',
    'trainer' => 'Ash',
    'owned' => '1999-05-21'
];

$client->request('PUT', '/test_couch/5a6a50b7c98499a4d8d69d4bfc00029a', [
    'headers' => [
        'Content-Type' => 'application/json'
    ],
    'body' => json_encode($doc)
]);

ドキュメントを削除するには、DELETEリクエストを使い、データベース名の直後にパスとしてドキュメントIDを渡し、それからqueryの数を最新リビジョンに渡します。

$client->request('DELETE', '/test_couch/7c7f800ee10a39f512a456339e0019f3', [
    'query' => [
        'rev' => '1-967a00dff5e02add41819138abb3284d'
    ]
]);

CouchDB Clientドクトリン

次に、CouchDB ClientでCouchDBデータベースを操作する方法を取り上げます。

まずCouchDBClientの新しいインスタンスを作成し、作業したいデータベース名が含まれる配列に渡します。

<?php
require 'vendor/autoload.php';

$client = \Doctrine\CouchDB\CouchDBClient::create(['dbname' => 'test_couch']);

それから配列として作成したいドキュメントを渡します。裏側ではCouchDBに受け入れられるJSON文字列に変換されます。

$client->postDocument([
    "name" => "Lucario",
    "type" => ["Fighting", "Steel"],
    "trainer" => "Korrina",
    "gender" => "f",
    "owned" => "2015-02-13"
]);

特定のビューを使用してドキュメントを検索するには、createViewQuery関数にデザインドキュメント名とビュー名を引数として渡します。すると、setKeyメソッドを使用したキーを設定できます。応答を得るにはexecuteメソッドを呼び出します。

$query = $client->createViewQuery('pokemon', 'filter_by_trainer');
$query->setKey('Ash');
$result = $query->execute();
echo "Pokemon trained by Ash: <br>";
foreach($result as $row){
    echo $row['value'] . "<br>";
}

次の結果を生成します。

Pokemon trained by Ash: 
Blastoise
Pikachu
Charizard
Talonflame
Froakie

ビューにreduce関数を追加した場合は、reduce関数が結果全体を単一のグループと見なさないようsetGroupメソッドを呼び出してtrueに設定します。setGrouptrueに設定するのは、すべての固有のキー(trainerフィールド)が単一のグループと見なされていることを意味します。

$query = $client->createViewQuery('pokemon', 'group_by_trainer');
$query->setReduce('true');
$query->setGroup('true');
$result = $query->execute();
foreach($result as $row){
    echo $row['key'] . "<br>";
    echo "Pokemon count: " . $row['value'] . "<br>";
    echo "<br>";
}

結果は次のようになります。

Ash
Pokemon count: 5

Brock
Pokemon count: 1

Elesa
Pokemon count: 1

Erika
Pokemon count: 1

Korrina
Pokemon count: 1

Misty
Pokemon count: 3

Ramos
Pokemon count: 1

Team Rocket
Pokemon count: 1

ドキュメントを更新するには、まずそのドキュメントの最新版を探します。なぜなら、CouchDBクライアントには、更新したいフィールドにだけ渡す機能がないからです。CouchDBは追加専用のデータベースなので、実際に更新する前に、常にドキュメントの最新版を取得し、必要なフィールドを更新または追加する必要があります。

$doc_id = '5a6a50b7c98499a4d8d69d4bfc003c9c';
$doc = $client->findDocument($doc_id);

$updated_doc = $doc->body;
$updated_doc['name'] = 'Golduck';
$updated_doc['owned'] = '1999-07-29';

$client->putDocument($updated_doc, $doc->body['_id'], $doc->body['_rev']);

ドキュメントの更新と同じく、最新のリビジョン番号を取得できるようデータベースにGETリクエストをします。そのあとdeleteDocument関数を呼び出し、ドキュメントIDと最新リビジョン番号を引数として渡します。

$doc_id = '7c7f800ee10a39f512a456339e0027fe';
$doc = $client->findDocument($doc_id);
$client->deleteDocument($doc_id, $doc->body['_rev']);

最後に

CouchDBは、パフォーマンスとバージョン管理に重点を置いた、ユーザーフレンドリーで使いやすいドキュメントデータベースです。

Postmanと2つのPHPクライアントを使った、CouchDBの使用方法と、異なるデータベース操作の実行方法を学びました。もっと詳しく知りたいなら、CouchDBについて必要な情報が網羅された最終ガイドをお勧めします。今後の記事ではさらに掘り下げ、これまでに学んだことを生かして適切なマルチプラットホームのアプリを構築します。

それまでは、彼らのスローガンが言うとおりリラックスしていてください。

(原文:A Pokemon Crash Course on CouchDB

[翻訳:柴田理恵/編集:Livit

Web Professionalトップへ

WebProfessional 新着記事