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

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

タグ:json

Node.js のアプリケーションで、以下のような処理を実装してみました:
 - 認証(ログイン)用の API には誰でもアクセスできる
 - 認証 API では ID とパスワードを与えて認証し、正しいユーザーにはトークンを発行する
 - 認証以外の主な API はこの発行されたトークンを使ってアクセスした時だけ実行を許可する
 - API 呼び出し時にトークンがなかったり、正しくなかった場合は実行せずにエラー


この仕組を実現するために JSON Web Tokens (以下 "JWT")を使いました:
https://jwt.io/

2017081400


JWT はオープンかつ Node.js ではスタンダードなトークンベースの認証ライブラリです。以下で紹介するサンプルでは Web フレームワークである Express や、POST データを扱う body-parser も合わせて使うので、まとめてインストールしておきます:
$ npm install express body-parser jsonwebtoken

これらのライブラリを使って以下のようなアプリケーションを作成します:
//. app.js

var express = require( 'express' );
var app = express();
var bodyParser = require( 'body-parser' );
var jwt = require( 'jsonwebtoken' );

//. アプリケーションサーバーの稼働ポート番号
var port = process.env.PORT || 8080

//. 任意のシークレット文字列を登録
app.set( 'superSecret', 'welovenodejs' );  //. 任意の文字列

app.use( bodyParser.urlencoded( { extended: false } ) );
app.use( bodyParser.json() );

//. ユーザー情報(本来は DB などに格納された情報を使う)
var users = [
  { name: 'user0', password: 'pass0', admin: true },
  { name: 'user1', password: 'pass1', admin: false },
  { name: 'user2', password: 'pass2', admin: false },
  { name: 'user3', password: 'pass3', admin: false }
];

//. ドキュメントルートへの GET は許可
app.get( '/', function( req, res ){
  res.send( 'Hello. The API is at http://localhost:' + port + '/api' );
});

//. API ROUTES
var apiRoutes = express.Router();

//. トークンなしでアクセスを許可する API を先に定義する

//. POST(http://localhost:8080/api/authenticate)
apiRoutes.post( '/authenticate', function( req, res ){
  for( var i = 0; i < users.length; i ++ ){
    if( users[i].name == req.body.name && users[i].password == req.body.password ){
      //. 認証したユーザーの情報を使ってトークンを生成
      var token= jwt.sign( users[i], app.get( 'superSecret' ), {
        expiresIn: '24h'
      });
      res.json( { success: true, message: 'Authentication successfully finished.', token: token } );
      return;
    }
  }

  res.json( { success: false, message: 'Authentication failed.' } );
  return;
});

//. ここより上で定義した API には認証フィルタはかけていない(そのまま使える) //. 認証フィルタ apiRoutes.use( function( req, res, next ){
//. ポスト本体、URLパラメータ、HTTPヘッダいずれかにトークンがセットされているか調べる var token = req.body.token || req.query.token || req.headers['x-access-token']; if( !token ){
//. トークンが設定されていなかった場合は無条件に 403 エラー return res.status(403).send( { success: false, message: 'No token provided.' } ); } //. 設定されていたトークンの値の正当性を確認 jwt.verify( token, app.get( 'superSecret' ), function( err, decoded ){ if( err ){ //. 正当な値ではなかった場合はエラーメッセージを返す return res.json( { success: false, message: 'Invalid token.' } ); } //. 正当な値が設定されていた場合は処理を続ける req.decoded = decoded; next(); }); }); //. 以下はトークンがないと使えない API //. GET(http://localhost:8080/api/) apiRoutes.get( '/', function( req, res ){ res.json( { message: 'Welcome to API routing.' } ); }); //. GET(http://localhost:8080/api/users) apiRoutes.get( '/users', function( req, res ){ res.json( users ); }); //. /api 以下に API をルーティング app.use( '/api', apiRoutes ); app.listen( port ); console.log( 'server started http://localhost:' + port + '/' );

肝になるのはルーティングに認証フィルタを定義している箇所です。ここよりも前(上)で定義した内容には認証フィルタは有効にならないので、認証なしで使える API となります(つまり GET / と POST /authenticate は認証していなくても使えます)。

この POST /authenticate API でポストデータ user, password を受取り、その値が変数 users の中で定義されているいずれかの組み合わせと一致していれば 24H 有効なトークンが発行されます(これを以下の API 実行時にパラメータ指定します)。

一方、ここよりも後(下)で定義する内容には認証フィルタが有効になり、GET /api と GET /api/users は上記方法で取得したトークンがパラメータに設定されていないと正しく処理されない API となります。


実際にこのアプリケーションを実行し($ node app)、curl コマンドで挙動を確認してみましょう。まずは問題なく実行できるはずの GET / を実行します:
$ curl -XGET 'http://localhost:8080/'
Hello. The API is at http://localhost:8080/api

問題なく実行できました。次は認証フィルタをかけた GET /api を実行してみます:
$ curl -XGET 'http://localhost:8080/api'
{"success":false,"message":"No token provided."}

API を実行した結果、「トークンがない」時のエラーメッセージが表示されました。ここも期待通りに動いています(試しませんが、ユーザー一覧を取得する GET /api/users も同様のエラーになります)。

では POST /authenticate で認証してトークンを取得してみますが最初はわざとパスワードを間違えてみます:
$ curl -XPOST -H 'Content-Type:application/json' 'http://localhost:8080/authenticate' -d '{"name":"user1","password":"pass0"}'
{"success":false,"message":"Authentication failed."}

先程同様にエラーになりましたが、エラーメッセージが「認証失敗」に変わりました。では改めて正しいパスワードを指定して実行してみます:
$ curl -XPOST -H 'Content-Type:application/json' 'http://localhost:8080/authenticate' -d '{"name":"user1","password":"pass1"}'
{"success":true,"message":"Authentication successfully finished.","token":"XXXXXX...XXXXXX"}

今度は認証が成功しました。レスポンスにも "token" が含まれています。最後にここで返ってきた token の値を指定して、先程アクセスできなかった GET /api を実行してみます:
$ curl -XGET 'http://localhost:8080/api?token=XXXXXX...XXXXXX'
{"message":"Welcome to API routing."}

今度は API が実行できました。同様にして GET /api/users も実行できるはずです:
$ curl -XGET 'http://localhost:8080/api/users?token=XXXXXX...XXXXXX'
[{"name":"user0","password":"pass0","admin":true},{"name":"user1","password":"pass1","admin":false},{"name":"user2","password":"pass2","admin":false},{"name":"user3","password":"pass3","admin":false}]


こんな感じで API の実行可否をトークンで制御できるようになりました。

今回の例ではソースコード内に静的に用意されたユーザー一覧を使って認証を行いましたが、実際の運用ではデータベース内に定義されたテーブル情報などを使うことになると思います。ただ JWT の基本的な考え方はこの1つのソースファイルだけで実現できているので、応用しやすいと思っています。

 

このブログでも何度か Node.js のネタを扱ってますが、非同期処理に悩まされることが多いので、自分の理解の意味でもまとめておくことにしました。

まず理解の大前提として、Node.js はシングルスレッドで動作するため、いわゆる並列処理はできない仕様になっています。そして非同期に処理を実行することができます。これによって並列処理ができなくても、何かの時間のかかる処理があった場合にその終了を待たずに次の処理に進むことができることを意味しています。 ただ、この辺りがややこしく難解になっていることも事実です。

例えば REST API などの HTTP リクエストを行って、その結果が得られたら、その得られた結果の JSON オブジェクトの値を使って処理をする、などというよくあるケースでもこの問題に直面します。

まずは何が難解なのかを紹介します。REST API だとローカル環境で気軽に試せないので、わざと実行に時間のかかる関数を用意して説明します。

例えばこのような処理を考えてみます:
// test.js

// わざと1秒かけてから、パラメータの2倍の数値を出力する関数
function func1( x ){
  setTimeout( function(){
    console.log( 2 * x );
  }, 1000 );
}

// 初期値を設定して出力
var n0 = 10;
console.log( 'n0 = ' + n0 );

// 初期値を上記関数のパラメータに入れて実行
func1( n0 );

この中では func1 という関数を定義しています。setTimeout を使ってわざと1秒(1000ミリ秒)待ってから、パラメータの値の2倍を画面に出力する、という関数です。 この関数を n0 (=10 に設定)という変数をパラメータにして実行する、というものです。なので "20" という結果が出力されることを期待しています。

この内容を test.js というファイルに保存して、実行してみます(青字部分が出力結果):
$ node test
n0 = 10
20

期待通りに "20" と出力されました。とりあえずここまでは成功です。


さて問題はここからです。上記例では関数 func1 の中で console.log が実行されて出力までを行いました。これを func1 からは与えた数値を2倍した結果を受け取るようにして、func1 の外側で出力するように変更してみます。深く考えずにやるとこんな感じでしょか?
// test.js(注 正しく動きません)

// わざと1秒かけてから、パラメータの2倍の数値を出力する関数
function func1( x ){
  setTimeout( function(){
    console.log( 2 * x );
  }, 1000 );
}

// わざと1秒かけてから、パラメータの2倍の数値を戻す関数
function func2( x ){
  setTimeout( function(){
    return ( 2 * x );
  }, 1000 );
}

// 初期値を設定して出力
var n0 = 10;
console.log( 'n0 = ' + n0 );

// 初期値を上記関数のパラメータに入れて実行し、戻り値を出力
var n1 = func2( n0 );
console.log( 'n1 = ' + n1 );

func1 をほぼコピペして func2 という関数を作りました。console.log の代わりに return にして、値を返すようにしています。呼び出し元からはこの func2 を実行して、得られた結果を出力するようにしています。

これを先程と同様に実行するとこうなります:
$ node test
n0 = 10
n1 = undefined

n0(10)の値の2倍の "20" という結果を期待していたのですが、"undefined" と表示されてしまいます(正確に書くと、上記の2行はすぐに表示されますが、更に1秒くらい経過してから終了します)。この理由は最初に書いたように func2 は非同期に実行されているので、return の行が実行されるまでには1秒かかります。しかしその前に(return の行が実行される前に)関数そのものの処理は終了してしまいます。つまり値が戻る前に(戻っていない値を受け取ることになっている)n1 という変数を出力しているので "undefined" になっているのでした。

このように、期待通りに動かなかった理由は明白なのですが、ではどうすればこの関数が期待通りに動く(1秒後に与えられた結果を戻り値として戻し、受け取った側がその値を出力する)ようにできるでしょうか?これが今日紹介する大きなテーマです。


結論を先に紹介すると、ここで Promise オブジェクトを使って関数を修正し、受け取った側もその変更に合わせて一部書き直す必要があります。具体的には以下のように修正します:
// test.js

// わざと1秒かけてから、パラメータの2倍の数値を出力する関数
function func1( x ){
  setTimeout( function(){
    console.log( 2 * x );
  }, 1000 );
}

// わざと1秒かけてから、パラメータの2倍の数値を戻す関数
function func2( x ){
  return new Promise( function( resolve ){
    setTimeout( function(){
      resolve( 2 * x );
    }, 1000 );
  });
}

// 初期値を設定して出力
var n0 = 10;
console.log( 'n0 = ' + n0 );

// 初期値を上記関数のパラメータに入れて実行し、戻り値を出力
func2( n0 ).then( function( n1 ){
  console.log( 'n1 = ' + n1 );
});

まず関数 func2 側は、Promise オブジェクトを新規に作成します。Promise オブジェクトは処理が成功した場合の関数をパラメータに指定します。上図だと
function( resolve ){
  setTimeout( function(){
    resolve( 2 * x );
  }, 1000 );
}

という関数がパラメータに指定されているので、成功するとこの関数が実行されます(今回は使っていませんが、第二パラメータを指定した場合は失敗時に実行する処理を指定したことになります)。この処理の中で1秒待って、指定したパラメータを2倍して resolve とする、ということになります。

そしてこの関数 func2 を呼び出す側も少し変更が必要になります。 func2() 関数の実行結果をそのまま変数として受け取るのではなく、成功した場合(今回の例だと1秒待って2倍になった値が返された場合)の処理を .then() 内に渡して処理することになります。この then 内の処理で計算結果(resolve で処理された内容)を n1 という変数で受け取って console.log で表示する、という内容にしています。

こうして修正した test.js を実行すると、以下のような結果になります(実際には n0 = 10 がすぐに表示され、1秒くらい待ってから n1 = 20 の行が表示されて終了します):
$ node test
n0 = 10
n1 = 20

Node.js の関数内で非同期処理を実行して、その非同期処理の終了を待って値を受け取るような関数を作る場合は、Promise オブジェクトを使って上記のように記述します、という紹介でした。非同期実行に慣れていないと、この辺りで戸惑うことが多いと感じたので、まとめておきました。


IBM Bluemix を通じて提供されているコグニティブサービス(認識型人工知能)API の1つに Tradeoff Analytics があります:
2015051800


この API を理解するにはデモサイトを参照していただくのがいいと思います:
http://tradeoff-analytics-demo.mybluemix.net/

このデモサイトでは「一覧の中で、自分にあったスマホはどれか?」という命題を、その人が重要視するスペックポイントを明確にした上で候補を上げていく、というものです。

スマホはメモリは多い方がいいし、バッテリーは多い方がいいけど、そうすると重量が重くなります。でも重量は気にする人もいればいない人もいます。また価格は安い方がいいに決まっていますが、メモリやバッテリー、重量などの要素と比べて重要度が高いのか低いのか、は人によって異なってきます。 全てを満たすパーフェクトなスマホはそうそう出てこないでしょうから、結局はどの要素を重要視するかでトレードオフを考えることになると思いますが、どの要素を重要視した場合はどのような結果になるか、を判断してくれる、そんな API です。

例えば上記デモサイトを開くと、デフォルトでは以下の様な表が表示されます:
2015051801


一番上の列がヘッダで、2行目からがスマホの候補リストになっています。表の3列目以降がスペックになっていて、3列目は価格(小さい方がいい)、4列目はRAMメモリ量(多い方がいい)、5列目はスクリーンサイズ(意見が別れるところですが、ここでは大きい方がいい)、6列目はカメラの画素数(多い方がいい)、7列目はメモリ量(多い方がいい)、8列目はバッテリー容量(多い方がいい)、そして9列目は重量(少ない方がいい)となっています。このうちデフォルト状態では価格、スクリーンサイズ、重量の色が緑になっていて、今はこの3点を重要視する人にとってのトレードオフを解析する指定になっています。

もしもこれら以外の点を重要視したい場合は表右下の "View/Edit JSON" と書かれた箇所をクリックして JSON の内容を変更します。例えば「スクリーンサイズは気にしないけど、バッテリー容量は重要!」という場合は以下のように値を変更します。 まずスクリーンサイズは今回意識しなくていいので、JSON 内の columns の中で "key" 値が "screen_size" になっている項目の "is_objective" 値を true から false に変更します。この "is_objective" の値が true のものはトレードオフの考慮に含めて、false のものは含めない、という指定です。なお "goal" 値が "MAX" のものは「(メモリ量など)大きい方がいい」という値、"MIN" のものは「(価格など)小さい方がいい」という値だという指定です:
{
  "key": "screen_size",
  "full_name": "Screen size (inch)",
  "type": "NUMERIC",
  "is_objective": false,
  "goal": "MAX"
},

続いてバッテリー容量はトレードオフに新たに含めたいので、同様にして "key" 値が "battery" になっている項目の "is_objective" 値を false から true に変更します:
{
  "key": "battery",
  "full_name": "Battery (mAh)",
  "type": "NUMERIC",
  "is_objective": true,
  "goal": "MAX"
},

この状態で編集エリア右下の "Back to table" をクリックすると、価格とバッテリー容量と重量の3点が重要視指定された状態の表が表示されるはずです。なお、これら以外でも JSON を変更することで候補スマホの名称や各数値を変更することもできます。全て JSON フォーマットで指定されていることを確認しておいてください:
2015051802


重要視する項目が決まったら、画面右下の "Analyze Sample Data" と書かれたボタンをクリックして、トレードオフを計算します:
2015051803


すると画面下に以下のようなグラフが表示されて、指定した3項目でのトレードオフを考えた結果が表示されます。各スマホアイテムがそれぞれの要素を意識した時にどの位置にあるのか、ということが視覚化されています:
2015051804


例えば、ここでバッテリー項目の下限のスライダーを 1000 から 1500 近くまで移動させると、「バッテリー容量が指定値(1500)以下のものは考慮しない」という指定をしたことになり、画面上部付近にあったいくつかのバッテリー容量の少ないスマホが画面から消えます。これで更に絞込みやすくなります:
2015051805


・・・と、なんとなく「トレードオフ解析」の意味がお分かりいただけたでしょうか? IBM Bluemix で用意されているのは、このような解析を行うための(与えられた条件から、上記の結果グラフを書くための)API が用意されている、という点が特徴です。つまり上記デモサイトのようなことを皆さんの作るアプリケーションの中でも実現できるようになる、ということです。

実際にこの API を利用するには、IBM Bluemix で上記の Tradeoff Analytics サービスをランタイムアプリケーションにバインドして資格情報を確認し、利用のための username と password を取得しておく必要があります
2015051801


ここまで準備できれば API そのものは単純です。取得した username と password を使って Basic 認証を行い、https://gateway.watsonplatform.net/tradeoff-analytics-beta/api/v1/dilemmas に対して、解析させたい JSON(上記の Edit JSON をクリックして編集したもの)をポストするだけです。

成功すると、以下の様な JSON が結果として返ってきます:
{
  "problem": {
    "columns" : [ {
      "key" : "price",
      "format" : "",
        :
        (ポストした JSON の内容)
        :
  },
  "resolution" : {
    "solutions" : [ {
      :
      :
    } ],
    "map" : [ {
      "nodes" : [ {
        "coordinates" : { "x":0.0, "y":0.0 },
        "solution_refs" : []
      }, {
        "coordinates" : { "x":1.0, "y":0.0 },
        "solution_refs" : []
      }, {
        "coordinates" : { "x":2.0, "y":0.0 },
        "solution_refs" : []
      }, {
        "coordinates" : { "x":3.0, "y":0.0 },
        "solution_refs" : []
      }, {
        "coordinates" : { "x":4.0, "y":0.0 },
        "solution_refs" : []
      }, {
        "coordinates" : { "x":5.0, "y":0.0 },
        "solution_refs" : []
      }, {
        "coordinates" : { "x":6.0, "y":0.0 },
        "solution_refs" : [ "5" ]
      }, 
        :
        :
      } ],
      "anchors" : [ {
        "name" : "price",
        "position" : { "x":0.0, "y":0.0 }
      }, {
        "name" : "battery",
        "position" : { "x":4.5, "y":7.8 }
      }, {
        "name" : "weight",
        "position" : { "x":9.0, "y":0.0 }
      } ],
        :
        :
    }
  }
}

結果の JSON の細かい内容は以下のリファレンスを参照していただきたいのですが、大きく3つの箇所を参照することになります。 1つ目は "problem" で、この中にポストした JSON がそのまま記述されているはずです。つまり「この問題に対する実行結果である」ことを示しています。

2つ目は "resolutions" の中の "map" で、ここに散布図を書いた時の各点の座標(x,y)が示されており、またその座標位置に相当するアイテムがあった場合は "solutions_refs" という配列値で示されます。例えば上の例ですと ( 6.0, 0.0 ) の位置に "key" 値が "5" に相当するアイテムがある、ということになります。

最後の3つ目は "resolutions" の中の "anchors" で、これが指定した各トレードオフ要素(この例では価格とバッテリー容量と重量)が散布図上のどの位置にあるかを示しています。


後はこの結果を受け取った側でどのように見せるか、という問題になります。チャート描画の API を使ってもいいし、Canvas を使って自分で記述してもいいと思います。ここから先は開発者やデザイナーの腕の見せ所と言えます。そういう意味ではここのデモサイトはなかなか良くできてますよね。


というわけで、これまでにこのブログで紹介してきたコグニティブサービスとは少し(特に実行方法の面で)毛色が違うサービスと言えます。API 実行時に指定フォーマットの JSON を作る必要があるのが少し面倒ですが、トレードオフを意識する要素と、それらの値は大きい方が嬉しいのか小さい方が嬉しいのかの情報、そしてアイテムの一覧を JSON にしてポストすれば結果が返ってくる、というものです。REST API そのものはシンプルなのでエクセルのような表計算からも実行できると思っています(エクセルだと逆に結果の視覚化が難しいかも・・)。


なお、ここで紹介した Watson Tradeoff Analytics API について、詳しくは API リファレンスを参照してください:
http://www.ibm.com/smarterplanet/us/en/ibmwatson/developercloud/apis/#!/tradeoff-analytics
 

IBM DB2 V10.5 ではテクノロジープレビューという正式サポート前の扱いですが、NoSQL 機能が使えるようになっています。

これは MongoDB のような JSON オブジェクトストア機能であり、実際に MongoDB のコマンドラインクライアントからも利用することができます。

このテクノロジープレビュー機能を有効にして、実際に使ってみるまでの手順を紹介します。なお環境は CentOS 6(64bit) 版を前提とします。


まず IBM DB2 V10.5 を導入します。DB2 は無償版の Express-C エディションが提供されているのでそれを使います。詳しい手順はこちらのエントリを参照ください(エントリはバージョン10.1での説明ですが、2015年1月5日現在、同じ手順で最新のバージョン10.5がダウンロード&導入できます):
DB2 Express-C (10.1) を CentOS にインストール&セットアップする


併せて MongoDB を導入します。詳しい手順はこちらを参照ください(この手順では MongoDB サーバーも導入されますが、今回はクライアントのみ使います):
mongoDB を CentOS 上にインストールする


最後に DB2 の NoSQL 機能を有効化する手順の中で JRE が必要になるため、Java 実行環境を導入します:
# yum install java-1.7.0-openjdk


ここまでの準備ができたら実際に DB2 の NoSQL 機能のセットアップを行います。システム上で db2inst1 ユーザーに切り替えて、NoSQL 用のデータベース jsondb をコードセット UTF-8、テリトリー jp で作成します(異なる内容で作成する場合はコマンドの赤字部分を該当内容に合わせて変更してください):
# su - db2inst1
$ db2 create database jsondb automatic storage yes using codeset utf-8 territory jp collate using system

しばらく待つと NoSQL データベース jsondb が作成されます。プロンプトが戻ったら続けて DB2 のポートを開きます。そして DB2 を再起動(ストップ&スタート)して、この内容を反映させます:
$ db2set DB2COMM=TCPIP
$ db2 update dbm cfg using svcename 50000
$ db2stop
$ db2start

そして db2inst1 ユーザーの ~/.bashrc を開いて編集し、環境変数の設定を行います:
$ vi ~/.bashrc
  :
  :
(最後に以下を追加して保存)
export JAVA_HOME=/usr/lib/jvm/jre-1.7.0-openjdk.x86_64
export PATH=$PATH:/opt/ibm/db2/V10.5/json/bin
export CLASSPATH=$CLASSPATH:/opt/ibm/db2/V10.5/json/lib/nosqljson.jar:/opt/ibm/db2/V10.5/json/lib/mongo-2.8.0.jar:/opt/ibm/db2/V10.5/json/lib/js.jar:/opt/ibm/db2/V10.5/json/lib/db2NoSQLWireListener.jar

上記で作成した jsondb データベースは初期状態では無効になっているので、コマンドラインから有効になるよう指定します。改めて db2inst1 ユーザーでログインし(或いは上記の ~/.bashrc の変更を反映し)、以下のコマンドを実行します:
# su - db2inst1
$ cd /opt/ibm/db2/V10.5/json/bin
$ ./db2nosql.sh -user db2inst1 -hostName localhost -port 50000 -db jsondb -setup enable -password (db2inst1ユーザーのパスワード)

最後に通信リスナーを MongoDB と同じ 27017 番ポートで起動します(db2inst1 ユーザーで、/opt/ibm/db2/V10.5/json/bin ディレクトリ上で実行する必要があります):
$ cd /opt/ibm/db2/V10.5/json/bin
$ ./wplistener.sh -start -mongoPort 27017 -userid db2inst1 -password (db2inst1ユーザーのパスワード) -dbName jsondb -logPath /tmp

   :
--- The wire listener is being started ---
Starting the channel listener on port 27017

上記のように、画面に "Starting the chennel listener on port 27017" と表示されれば DB2 の NoSQL データベース機能の起動に成功しています。


最後に動作確認の意味で、この DB2 上で動いている NoSQL データベース jsondb に、MongoDB クライアントから接続してみましょう。同じシステム上の別端末を開いて、mongo コマンド(MongoDB クライアント)を実行してみます(青字はシステムからの出力結果です):
# mongo
MongoDB shell version: 2.6.6
connecting to: test
>

エラーが表示されていなければ接続できたことになります。続けて jsondb データベースに接続して、myfirstcollection コレクションに "_id" = 100 の JSON 文書を作成/更新/検索してみます:
> use jsondb (DB変更)
switched to db jsondb
> db.myfirstcollection.insert( { "_id":100, "name":"K.Kimura" } ) (文書作成)
Cannot use commands write mode, degrading to compatibility mode
WriteResult({ "nInserted" : 1 })
> db.myfirstcollection.findOne() (検索)
{ "_id" : 100, "name" : "K.Kimura" } (成功)
> db.myfirstcollection.update( { "_id":100 }, {"name":"Kei KIMURA" } ) (更新)
WriteResult({ "nMatched" : 1, "nUpserted" : 0 }) (成功)
> db.myfirstcollection.findOne() (再検索)
{ "_id" : 100, "name" : "Kei KIMURA" } (成功)
  :
  :
> quit()
#

MongoDB クライアントからの一通りのコマンドは全て成功しているようです。


mongo の基本的なコマンドに関しては全て動いているようですが、どの程度の互換性があるのか/ないのか、MongoDB のライブラリが使えるのか/使えないのか、まだちょっとわかりません。 でもこれが使えるようであれば、PHP から MongoDB ライブラリを経由して DB2 にアクセスする、といった、これまでできなかったような使い方も広がってくると思います。この機能が正式サポートされるのが楽しみです。




(参考)
http://www.ibm.com/developerworks/jp/data/library/techarticle/dm-1306nosqlforjson4/
 

今年(2014年)の10月31日より、オープンデータ高度化の一環で総務省統計局より政府統計データの多くが Web API にて公開されました:
API機能で利用できる統計データの拡充 -統計におけるオープンデータの高度化- (PDF)

この公開された政府統計データ(eStat)を使ったサンプルを紹介します。
top_header_logo



まず、この政府統計データを使う上での利用者登録と、アプリケーション登録が必要です。利用者登録は以下のページから行います:
利用登録・ログイン

メールアドレスやパスワードを登録し、最終的にアクティベートまで行い、利用登録が完了すると上記ページからログインできるようになります。

ログイン後にアプリケーション登録を行います。「アプリケーションIDの取得」をクリックして、アプリケーションの名称、URL 、概要を入力し、「発行」ボタンをクリックすると appId と書かれた箇所にアプリケーション ID が表示されます。これは後から使う情報になるのでメモするなどしておきます(以下、appId を XXXXXXXXXX として説明を続けます):
2014121501


なお、1つの利用IDで最大3つまでのアプリケーションが登録できます。ウェブ上に公開されているアプリケーションから利用する場合はその URL を指定する必要があります。公開されていないアプリや、テスト目的で利用したい場合はURLに http://localhost/ を指定すればいいようです。


アプリケーションの登録が完了し、appId が取得できたら早速 API を利用してみましょう。まずはどのような統計データが公開されているのか、その一覧を取り出してみます。

データリストを取得するには、以下の URL に GET アクセスします:
http://statdb.nstac.go.jp/api/1.0b/app/getStatsList?appId=XXXXXXXXXX&statsCode=ZZZZ

getStatsList では URL パラメータが2つ指定されています。appId には上記で取得したアプリケーション ID を、そして statsCode には政府統計コードを指定します。例えば平成22年に実施・更新された国勢調査のデータを取得するのであれば、政府統計コードは 00200521 になるので、こんな感じです:
http://statdb.nstac.go.jp/api/1.0b/app/getStatsList?appId=XXXXXXXXXX&statsCode=00200521
(XXXXXXXXXX = 取得したアプリケーションID)

結果は以下の様な XML フォーマットで取得できます:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<GET_STATS_LIST xsi:noNamespaceSchemaLocation="http://statdb.nstac.go.jp/api/1.0b/schema/GetStatsList.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <RESULT>
  <STATUS>0</STATUS>
  <ERROR_MSG>正常に終了しました。</ERROR_MSG>
  <DATE>2014-12-15T15:15:36.690+09:00</DATE>
 </RESULT>
 <PARAMETER>
  <LANG>J</LANG>
  <STATS_CODE>00200521</STATS_CODE>
 </PARAMETER>
 <DATALIST_INF>
  <NUMBER>4180</NUMBER>
  <LIST_INF id="0000030001">
   <STAT_NAME code="00200521">国勢調査</STAT_NAME>
   <GOV_ORG code="00200">総務省</GOV_ORG>
   <STATISTICS_NAME>昭和55年国勢調査 第1次基本集計 全国編</STATISTICS_NAME>
   <TITLE no="00101">男女の別(性別)(3),年齢5歳階級(23),人口 全国・市部・郡部・都道府県(47),全域・人口集中地区の別</TITLE>
   <CYCLE>-</CYCLE>
   <SURVEY_DATE>198010</SURVEY_DATE>
   <OPEN_DATE>2007-10-05</OPEN_DATE>
   <SMALL_AREA>0</SMALL_AREA>
  </LIST_INF>
  <LIST_INF id="0000030002">
   <STAT_NAME code="00200521">国勢調査</STAT_NAME>
   <GOV_ORG code="00200">総務省</GOV_ORG>
   <STATISTICS_NAME>昭和55年国勢調査 第1次基本集計 全国編</STATISTICS_NAME>
   <TITLE no="00102">男女の別(性別)(3),年齢各歳階級(103),人口 全国・市部・郡部・都道府県(47),全域・人口集中地区の別</TITLE>
   <CYCLE>-</CYCLE>
   <SURVEY_DATE>198010</SURVEY_DATE>
   <OPEN_DATE>2007-10-05</OPEN_DATE>
   <SMALL_AREA>0</SMALL_AREA>
  </LIST_INF>
  <LIST_INF id="0000030003">
    :
  </LIST_INF>
    :
    :
 </DATALIST_INF>
</GET_STATS_LIST>

<DATALIST_INF> の中に複数の <LIST_INF> 要素があり、それら1つ1つが異なる統計データを示しています。また個々の統計データにはデータの説明文なども含まれており、 <LIST_INF id="YYYYYYYYYY"> の YYYYYYYYYY 部分が ID として区別されています。これでどのような統計データがあるか、の一覧が管理されています。


また、統計データの種類を1つ指定して、実際の統計データを取得するには getStatsData を使います:
http://statdb.nstac.go.jp/api/1.0b/app/getStatsData?appId=XXXXXXXXXX&statsDataId=YYYYYYYYYY

パラメータの appId には getStatsList の時と同様にアプリケーション ID を、また statsDataId には getStatsList で取得したデータID(上記の赤字で示されている部分)を指定します。 上記例の一番上にある「昭和55年国勢調査の年齢5歳階級別の人口データ」であれば 0000030001 なので、以下の様な URL になります:
http://statdb.nstac.go.jp/api/1.0b/app/getStatsData?appId=XXXXXXXXXX&statsDataId=0000030001

そして、この結果は以下の様な XML フォーマットで取得できます:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<GET_STATS_DATA xsi:noNamespaceSchemaLocation="http://statdb.nstac.go.jp/api/1.0b/schema/GetStatsData.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <RESULT>
  <STATUS>0</STATUS>
  <ERROR_MSG>正常に終了しました。</ERROR_MSG>
  <DATE>2014-12-15T15:15:38.380+09:00</DATE>
 </RESULT>
 <PARAMETER>
  <LANG>J</LANG>
  <STATS_DATA_ID>0000030001</STATS_DATA_ID>
  <DATA_FORMAT>X</DATA_FORMAT>
  <START_POSITION>1</START_POSITION>
  <METAGET_FLG>Y</METAGET_FLG>
 </PARAMETER>
 <STATISTICAL_DATA>
  <TABLE_INF id="0000030001">
   <STAT_NAME code="00200521">国勢調査</STAT_NAME>
   <GOV_ORG code="00200">総務省</GOV_ORG>
   <STATISTICS_NAME>昭和55年国勢調査 第1次基本集計 全国編</STATISTICS_NAME>
   <TITLE no="00101">男女の別(性別)(3),年齢5歳階級(23),人口 全国・市部・郡部・都道府県(47),全域・人口集中地区の別</TITLE>
   <SURVEY_DATE>198010</SURVEY_DATE>
   <TOTAL_NUMBER>3651</TOTAL_NUMBER>
   <FROM_NUMBER>1</FROM_NUMBER>
   <TO_NUMBER>3651</TO_NUMBER>
  </TABLE_INF>
  <CLASS_INF>
   <CLASS_OBJ id="cat01" name="全域・集中の別030002">
   <CLASS code="00700" name="全域" level="1"/>
   <CLASS code="00701" name="人口集中地区" level="1"/>
  </CLASS_OBJ>
  <CLASS_OBJ id="cat02" name="男女A030001">
   <CLASS code="000" name="男女総数" level="1"/>
   <CLASS code="001" name="男" level="1"/>
   <CLASS code="002" name="女" level="1"/>
  </CLASS_OBJ>
  <CLASS_OBJ id="cat03" name="年齢5歳階級A030002">
   <CLASS code="000" name="総数" level="1" unit="人"/>
   <CLASS code="001" name="0-4歳" level="1" unit="人"/>
   <CLASS code="002" name="5-9歳" level="1" unit="人"/>
   <CLASS code="003" name="10-14歳" level="1" unit="人"/>
     :
  </CLASS_OBJ>
  <CLASS_OBJ id="area" name="全国都道府県030001">
   <CLASS code="00000" name="全国" level="1"/>
   <CLASS code="00001" name="全国市部" level="1"/>
   <CLASS code="00002" name="全国郡部" level="1"/>
   <CLASS code="01000" name="北海道" level="1"/>
   <CLASS code="02000" name="青森県" level="1"/>
   <CLASS code="03000" name="岩手県" level="1"/>
     :
  </CLASS_OBJ>

  <DATA_INF>
   <NOTE char="***">当該数値がないもの</NOTE>
   <NOTE char="-">当該数値がないもの</NOTE>
   <VALUE cat01="00700" cat02="000" cat03="000" area="00000" time="1980000000" unit="人">117060396</VALUE>
   <VALUE cat01="00700" cat02="000" cat03="000" area="00001" time="1980000000" unit="人">89187409</VALUE>
   <VALUE cat01="00700" cat02="000" cat03="000" area="00002" time="1980000000" unit="人">27872987</VALUE>
   <VALUE cat01="00700" cat02="000" cat03="000" area="01000" time="1980000000" unit="人">5575989</VALUE>
   <VALUE cat01="00700" cat02="000" cat03="000" area="02000" time="1980000000" unit="人">1523907</VALUE>
   <VALUE cat01="00700" cat02="000" cat03="000" area="03000" time="1980000000" unit="人">1421927</VALUE>
   <VALUE cat01="00700" cat02="000" cat03="000" area="04000" time="1980000000" unit="人">2082320</VALUE>
   <VALUE cat01="00700" cat02="000" cat03="000" area="05000" time="1980000000" unit="人">1256745</VALUE>
   <VALUE cat01="00700" cat02="000" cat03="000" area="06000" time="1980000000" unit="人">1251917</VALUE>
     :
  </DATA_INF>
 </STATISTICAL_DATA>
</GET_STATS_DATA>

この結果(<STATISTICAL_DATA>)は以下のように読み解けます。

まず、<CLASS_INF> を見ることで、データは4つのカテゴリ分類がされています。まず cat01 が「全域(00700)か、人口集中地区(00701)か」を示しています。

次に cat02 は男女別分類をしています。男女総数のデータ(000)なのか、男性のみのデータ(001)なのか、それとも女性のみのデータ(002)なのか、という分類のようです。

そして cat03 は年齢を5歳区切りにした分類です。全年代(000)、0~4歳(001)、5~9歳(002)、・・といった具合で分類されていることが分かります。

最後に area が都道府県別分類をしていることが分かります。全国(00000)、全国市部(00001)、全国郡部(00002)、北海道(01000)、青森(02000)、・・・といった具合のようです。


続いて <DATA_INF> 内の <VALUE> 部分が実際の数値データになっています。例えば先頭にあるこのデータ:
<VALUE cat01="00700" cat02="000" cat03="000" area="00000" time="1980000000" unit="人">117060396</VALUE>
これは cat01=00700, cat02=000, cat03=000, area=0000 という属性が付与されているので、エリアは全域、男女総数、全年代の全国での人口総数を意味していて、その数値が 117060396 (人)である、ということが分かります。

したがって、例えば年代毎の全国の人口分布を調べたいのであれば、cat01=00700, cat02=000, area=00000 で固定して、cat03 が 001 以上のものをフィルタリングして調べればよい、ということになりますね。 


なお、Web API の app と getStats***** の間に json を入れると、出力結果が XML ではなく JSON フォーマットになります。JSON で必要な場合はこちらを使ってください:
http://statdb.nstac.go.jp/api/1.0b/app/json/getStatsList?appId=XXXXXXXXXX&statsCode=00200521
http://statdb.nstac.go.jp/api/1.0b/app/json/getStatsData?appId=XXXXXXXXXX&statsDataId=0000030001






 

このページのトップヘ