LoginSignup
3

More than 5 years have passed since last update.

RethinkDB on Docker - Part2: 10 Minutes

Posted at

前回少しだけRethinkDBを使ってみてとてもいい感じで使いやすいです。APIリファレンスもNode.jsPythonRubyとシンプルでみやすく充実しています。The Thinkerのマスコットもかわいいです。Thirty-second quickstart with RethinkDBに続いてチュートリアルのTen-minute guide with RethinkDB and JavaScriptを試してみます。

10 Minutes

プログラムを理解してしまえば10分で動きますが、Node.jsの動くプログラムを書くにはもう少し時間がかかります。こちらはNode.jsの他に、PythonRuby用が用意されています。

プロジェクト

Node.jsのプログラムもDockerで動かすのでプロジェクトを作成します。

$ cd ~/rethinkdb_apps/node-rethinkdb
$ tree -L 1
.
├── Dockerfile
├── app.js
├── node_modules -> /dist/node_modules
└── package.json

DockerfileはオフィシャルのNode v0.12.3です。

~/rethinkdb_apps/node-rethinkdb/Dockerfile
FROM node:0.12
MAINTAINER Masato Shimizu <ma6ato@gmail.com>

RUN mkdir -p /app
WORKDIR /app

COPY package.json /app/
RUN mkdir -p /dist/node_modules && ln -s /dist/node_modules /app/node_modules && \
  npm install
COPY . /app
CMD ["npm", "start"]

node_modulesをDockerホスト側でキャッシュするために/dist/node_modules/をホスト側にシムリンクを作ります。

$ cd ~/rethinkdb_apps/node-rethinkdb
$ ln -s /dist/node_modules .

package.jsonはRethinkDBと非同期処理のためにasyncを使います。

~/rethinkdb_apps/node-rethinkdb/package.json
{
  "name": "node-rethinkdb",
  "description": "Node RethinkDB 10 minutes",
  "version": "0.0.1",
  "private": true,
  "dependencies": {
    "rethinkdb": "^2.0.0-1",
    "async" : "^1.0.0"
  },
  "scripts": {"start": "node app.js"}
}

Node.jsのサンプルは一連で動くようにasync.waterfallでまとめました。Node.jsのデータベースアクセスのサンプルプログラムなのでコールバックは多いですがわかりやすいAPIなので何をしているかすぐわかると思います。

~/rethinkdb_apps/node-rethinkdb/app.js
var r = require('rethinkdb'),
async = require('async');

console.log('=== Open a connection');
r.connect({host: process.env.RDB_PORT_8080_TCP_ADDR,
           port: 28015},
    function(err,conn){
        if (err) throw err;
        async.waterfall([            
            function(callback){
                console.log('=== Drop a table');
                r.db('test').tableDrop('authors').run(conn,callback);
            },
            function(res,callback) {
                console.log('=== Create a table');
                r.db('test').tableCreate('authors').run(conn,callback);
            },
            function(res,callback){
                console.log('=== Insert data');
                r.table('authors').insert([
                    { name: "William Adama", tv_show: "Battlestar Galactica",
                      posts: [
                          {title: "Decommissioning speech", content: "The Cylon War is long over..."},
                          {title: "We are at war", content: "Moments ago, this ship received word..."},
                          {title: "The new Earth", content: "The discoveries of the past few days..."}
                      ]
                    },
                    { name: "Laura Roslin", tv_show: "Battlestar Galactica",
                      posts: [
                          {title: "The oath of office", content: "I, Laura Roslin, ..."},
                          {title: "They look like us", content: "The Cylons have the ability..."}
                      ]
                    },
                    { name: "Jean-Luc Picard", tv_show: "Star Trek TNG",
                      posts: [
                          {title: "Civil rights", content: "There are some words I've known since..."}
                      ]
                    }
                ]).run(conn,callback);
            },
            function(res,callback){
                console.log(JSON.stringify(res, null, 2));
                callback(null);
            },
            function(callback){
                console.log('=== All documents in a table');
                r.table('authors').run(conn,callback);
            },
            function(cursor,callback){
                cursor.toArray(callback);
            },
            function(res,callback) {
                console.log(JSON.stringify(res, null, 2));
                callback(null);
            },
            function(callback){
                console.log('=== Filter documents based on a condition');
                r.table('authors')
                    .filter(r.row('name').eq("William Adama"))
                    .run(conn,callback);
            },
            function(cursor,callback){
                cursor.toArray(callback);
            },
            function(res,callback) {
                console.log(JSON.stringify(res, null, 2));
                callback(null);
            },
            function(callback) {
                console.log('=== Retrieve all authors who have more than two posts');
                r.table('authors')
                    .filter(r.row('posts').count().gt(2))
                    .run(conn,callback);
            },
            function(cursor,callback){
                cursor.toArray(callback);
            },
            function(res,callback) {
                console.log(JSON.stringify(res, null, 2));
                callback(null);
            },
            function(callback) {
                console.log('=== Update documents');
                r.table('authors')
                    .update({type: "fictional"})
                    .run(conn,callback);
            },function(res,callback) {
                console.log(JSON.stringify(res, null, 2));
                callback(null);
            },
            function(callback){
                console.log('=== Update a subset of documents by filtering the table first');
                r.table('authors')
                    .filter(r.row("name").eq("William Adama"))
                    .update({rank: "Admiral"})
                    .run(conn,callback);
            },function(res,callback){
                console.log(JSON.stringify(res, null, 2));
                callback(null);
            },
            function(callback){
                console.log('=== add an additional post');
                r.table('authors')
                    .filter(r.row("name").eq("Jean-Luc Picard"))
                    .update({posts: r.row("posts").append({
                             title: "Shakespeare",
                             content: "What a piece of work is man..."})})
                    .run(conn,callback);
            },function(res,callback){
                console.log(JSON.stringify(res, null, 2));
                callback(null);
            },
            function(callback){
                console.log('=== Delete documents');
                r.table('authors')
                    .filter(r.row('posts').count().lt(3))
                    .delete()
                    .run(conn,callback);
            },function(res,callback){
                console.log(JSON.stringify(res, null, 2));
                callback(null);
            },
            function(callback){
                console.log('=== Close a connection');
                conn.close(callback);                
            }
        ],function(err, result){
            if (err) throw err;
        });
});

Realtime feedsはバッチで実行できないので除外しました。レコードに更新があったときにプッシュ通知してくれるようです。

r.table('authors').changes().run(connection, function(err, cursor) {
    if (err) throw err;
    cursor.each(function(err, row) {
        if (err) throw err;
        console.log(JSON.stringify(row, null, 2));
    });
});

実行

作成したサンプルプログラムを実行します。Dockerのコンテナはlinkを使いRethinkDBのIPアドレスを環境変数から取得します。

$ cd ~/rethinkdb_apps/node-rethinkdb
$ docker build -t node-rethinkdb .
$ docker run --rm --name node-rethinkdb --link rethinkdb:rdb -v $PWD:/app node-rethinkdb

> node-rethinkdb@0.0.1 start /app
> node app.js

処理結果です。レコードの取得系ははJSONでレコードが返ります。登録、更新、削除系でもJSONで処理結果が返ります。

=== Open a connection
=== Drop a table
=== Create a table
=== Insert data
{
  "deleted": 0,
  "errors": 0,
  "generated_keys": [
    "f96b4b0f-5e5a-4484-a973-839038ae9725",
    "c398f4a0-bfb7-42c0-92d0-54830ee74460",
    "63137b0a-4d55-4635-8fd7-1bb1120ab8bd"
  ],
  "inserted": 3,
  "replaced": 0,
  "skipped": 0,
  "unchanged": 0
}
=== All documents in a table
[
  {
    "id": "f96b4b0f-5e5a-4484-a973-839038ae9725",
    "name": "William Adama",
    "posts": [
      {
        "content": "The Cylon War is long over...",
        "title": "Decommissioning speech"
      },
        "content": "I, Laura Roslin, ...",
        "title": "The oath of office"
      },
      {
        "content": "The Cylons have the ability...",
        "title": "They look like us"
      }
    ],
    "tv_show": "Battlestar Galactica"
  },
  {
    "id": "63137b0a-4d55-4635-8fd7-1bb1120ab8bd",
    "name": "Jean-Luc Picard",
    "posts": [
      {
        "content": "There are some words I've known since...",
        "title": "Civil rights"
      }
    ],
    "tv_show": "Star Trek TNG"
  }
]
=== Filter documents based on a condition
[
  {
    "id": "f96b4b0f-5e5a-4484-a973-839038ae9725",
    "name": "William Adama",
    "posts": [
      {
        "content": "The Cylon War is long over...",
        "title": "Decommissioning speech"
      },
      {
        "content": "Moments ago, this ship received word...",
        "title": "We are at war"
      },
      {
        "content": "The discoveries of the past few days...",
        "title": "The new Earth"
      }
    ],
    "tv_show": "Battlestar Galactica"
  }
]
=== Retrieve all authors who have more than two posts
[
  {
    "id": "f96b4b0f-5e5a-4484-a973-839038ae9725",
    "name": "William Adama",
    "posts": [
      {
        "content": "The Cylon War is long over...",
        "title": "Decommissioning speech"
      },
      {
        "content": "Moments ago, this ship received word...",
        "title": "We are at war"
      },
      {
        "content": "The discoveries of the past few days...",
        "title": "The new Earth"
      }
    ],
    "tv_show": "Battlestar Galactica"
  }
]
=== Update documents
{
  "deleted": 0,
  "errors": 0,
  "inserted": 0,
  "replaced": 3,
  "skipped": 0,
  "unchanged": 0
}
=== Update a subset of documents by filtering the table first
{
  "deleted": 0,
  "errors": 0,
  "inserted": 0,
  "replaced": 1,
  "skipped": 0,
  "unchanged": 0
}
=== add an additional post
{
  "deleted": 0,
  "errors": 0,
  "inserted": 0,
  "replaced": 1,
  "skipped": 0,
  "unchanged": 0
}
=== Delete documents
{
  "deleted": 2,
  "errors": 0,
  "inserted": 0,
  "replaced": 0,
  "skipped": 0,
  "unchanged": 0
}
=== Close a connection

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3