SummerWind

Web, Photography, Space Development

Node.jsとApache Thrift

この記事は、東京Node学園祭2012 アドベントカレンダーの27日目の記事です。いよいよ来週にせまった東京Node学園祭2012の開催も楽しみですね。

今さら感も少しありますが、最近はApache Thriftをよく使っています。言語を問わずサーバーとクライアントで同じオブジェクトを使うことができるRPCフレームワークなのですが、ちょっとしたAPIを作るのにも便利です。調べてみたところ、ThrfitはNode.js用のコードも出力できるようになっているので、今回はNode.jsでThriftを使うにはどうするのかをざっとまとめてみます。

Thriftを使うには、まずThrift本体のインストールが必要です。手元の開発環境はMac OS X 10.8 (Mountain Lion)なので、以下のようにHomebrewを使ってインストールしました。

$ brew install thrift

今回はNode.jsでThriftを使うサンプルとして、TwitterのようなTweetを投稿/取得するためのAPIを開発します。Tweetのデータは本文、ユニークなID、投稿日時 (UNIXタイム)の3つの情報を保持し、投稿用のpost、取得用のgetの2つのAPIを利用して操作することができます。APIの仕様に基づいてThriftの定義ファイルを以下のように作成し、tweet.thriftとして保存します。

// tweet.thrift

struct Tweet {
    1: string text,
    2: optional i32 id,
    3: optional i32 created_at
}

service TweetService {
    i32 post(1: Tweet tweet),
    Tweet get(1: i32 id)
}

続いて、Thriftを使って作成した定義ファイルをコードに変換します。変換後にgen-nodejsというディレクトリが作られ、TweetService.jsとtweet_types.jsの2つのファイルが生成されます。

$ thrift --gen js:node tweet.thrift
$ tree gen-nodejs
gen-nodejs
├── TweetService.js
└── tweet_types.js

コードへの変換できたところで、サーバーとクライアントの実装をおこないたいと思いますが、先にNode.js用のThriftモジュールのインストールが必要になります。モジュールはおなじみのnpmコマンドからインストールすることができます。

$ npm install thrift

Thriftモジュールをインストールしたら、サーバーの実装をおこないます。以下はサーバーの実装コードです。 Tweetの管理処理まわりはサンプルなので簡単に書いています。Thriftサービスの各関数の処理は、Thriftサーバーを生成する時に各関数ごとのコールバックとして実装する点と、各関数の戻り値もコールバックで渡す点にだけ注意をすれば、簡単に実装することができます。

// server.js

var thrift = require('thrift');

var TweetService = require('./gen-nodejs/TweetService');

// Tweet ID
var id = 0;
// Tweet ストレージ
var tweets = {};

// Thrift サーバーの生成
var server = thrift.createServer(TweetService, {
    // post コールバック
    post: function(tweet, result) {
        // Tweet IDを生成
        tweet.id = ++id;
        // created_at を UNIX タイムから生成
        tweet.created_at = parseInt(new Date() / 1000);
        // Tweet を保存
        tweets[tweet.id] = tweet;

        console.log("[Post]", tweet);
        result(tweet.id);
    },

    // get コールバック
    get: function(id, result) {
        console.log("[Get]", id);
        result(tweets[id]);
    }
});

// Thrift サーバーを起動
server.listen(8888);

サーバーの実装ができたら、クライアントも実装します。クライアントは先に作ったThriftサーバーに接続し、各サービスの関数を呼び出すような流れになります。サーバー側の処理を一般的なオブジェクトとして呼び出せて、読みやすいコードになりました。

// client.js

var thrift = require('thrift');

var TweetService = require('./gen-nodejs/TweetService'),
    Tweet        = require('./gen-nodejs/tweet_types').Tweet;

// Thrift サーバーに接続
var connection = thrift.createConnection('localhost', 8888);
connection.on("error", function(err) {
    console.error(err);
});

// クライアントを生成
var client = thrift.createClient(TweetService, connection);

// Tweet を生成
var tweet = new Tweet({ text: "<3 Node.js!" });

// Tweet を投稿
client.post(tweet, function(err, id) {
    // エラー処理
    if(err) {
        return console.error(err);
    }

    // Tweet ID を表示
    console.log("[Post]", id);

    // Tweet を取得
    client.get(id, function(err, posted_tweet) {
        // エラー処理
        if(err) {
            return console.error(err);
        }

        // 投稿済み Tweet を表示
        console.log("[Get]", posted_tweet);
        // 接続を解除
        connection.end();
    });
});

サーバーとクライアントの実装が完了したので、お互いに通信できるかを確認してみましょう。以下のように先にサーバーを起動してからクライアントを実行すると、Tweetが投稿され、その投稿されたTweetの情報が正しく出力さました。これにより、どちらのコードも正しく動作していることが分かりました。

$ node server.js > server.log &
$ node client.js
[Post] 1
[Get] { text: '<3 Node.js!', id: 1, created_at: 1352560191 }

ここまでで、Node.jsでThriftを使う方法をざっとまとめてみましたが、他の言語でThriftを使う時と同じように、Node.jsでも比較的簡単にThriftを扱うことができたかと思います。Node.jsではJSONを扱うAPIを作るほうが利用シーンとして多いかと思いますが、他の言語で書かれたAPIと通信して処理をする必要がある場面などでは、Thriftを使って簡単に実装するのも悪くないかなと思っています。

Moto Ishizawa

Moto Ishizawa
ソフトウェアエンジニア。ロケットの打上げを見学するために、たびたびフロリダや種子島にでかけるなど、宇宙開発分野のファンでもある。