2013-03-17 13:34:30
2 月はブログを書くことなく終わってしまいました。
3 月も半ばを過ぎようとしていますが、久々のブログ更新です。
ちょいとした Web アプリケーションを書くとき、やっぱり選んでしまうのは Sinatra。
とにかくサクっと作れるし、ファイルとかも少なくて済む。
中でもズボラ的に便利だと思うのがインラインテンプレートと呼ばれる機能です。
以下のように、プログラムの末尾にテンプレートをそのまま書けるというものです。
このインラインテンプレートがそれだけで gem 化されていれば、あらゆるフレームワークや DSL にこれを組み込めるのではないか、ということで作ってみました。
yuya-takeyama/inline_template_loader
inline_template_loader という名前で rubygems.org に登録済みですので、gem コマンドなり bundler なりでインストールできます。
使い方はこんな感じ。
InlineTemplateLoader.load でインラインテンプレートを Hash として読み込みます。
インラインテンプレートを読み込むファイルを load に引数として渡すこともできますが、省略時は caller を使ってバックトレースから呼び出し元を調べて、そのファイルから読み出すようにしています。
これを何らかのフレームワーク等に組み込む場合は、少し工夫が必要です。
普通は InlineTemplateLoader.load をラップして使うことになると思いますが、その場合は上記のように引数を省略してしまうと、ラッパーメソッドが存在するフレームワークのライブラリそれ自体からインラインテンプレートを読み込んでしまうことになります。
それだと困るので、引数に整数値を渡した場合は caller が返す配列のうち、整数値分だけ後の要素を呼び出し元とすることができます。
文だけだと訳がわからないと思うので、例を示します。
まずは DSL を用いた何らかのプログラム。
インラインテンプレートはこのファイルにあります。
次に InlineTemplateLoader をラップして何らかの処理を行う内部 DSL の処理系です。
このように load に整数値 (ここでは 4) を渡すことで、その文だけバックトレースを遡り、DslUsesInlineTemplateLoader.dsl の呼び出し元のファイルからインラインテンプレートを読むことができます。
来週あたりには、これを用いて MongoDB を操作する DSL なんかを紹介できればと思ってます。
そんじゃーね。
2013-01-13 13:16:57
あけましておめでとうございます。
年末年始は実家にも帰らず、Kindle Paperwhite
で本を読みあさったり、Hulu でプリティーリズムオーロラドリームを視て泣いたり、小林賢太郎の公演
を DVD で視て笑ったり、あとは近所の土手を走ったりしてきました。
1km あたり 4 分をなかなか切れずにいます。
そんな感じでインプット中心に年末年始を過ごしてしまったわけですが、そろそろアウトプット期に転換していこうということで、みんな大好き fluentd に以下のような Pull Request を送ってみました。
Config DSL by yuya-takeyama · Pull Request #97 · fluent/fluentd
今のところ Proof of Concept レベルで、マージをお願いできるようなレベルにはなってないと思います。
こういうのどうっすかね、という感じで識者の皆様にコメントなど賜りたいと思っている次第です。
とりあえずは既存の設定ファイルも今まで通り使えるように、fluentd 起動時の -c オプションで指定した設定ファイルの拡張子が .rb だったときだけ、DSL として解釈するようになってます。
これによって fluent.conf は以下のような fluent.conf.rb として書けるようになります。
また、複数のサーバに転送する場合など、イテレータを使えれば、コピペ要らずで DRY な感じの記述ができます。
他にも、対象サーバの一覧を REST API から取得する、みたいな感じにしてみれば、fluentd のクラスタ構成の変更をする際は、サーバリストを管理するサーバに情報を登録して、各クラスタにシグナルを送って再起動すれば反映される、みたいな自動化も柔軟にできるんじゃないでしょうか。
さて、私自身は今のところ fluentd を使っているわけではなかったりします。
ここ最近はデータ分析・閲覧のための基盤を MongoDB や GrowthForecast 等を使って構築しているのですが、各サーバ感のログのやり取りが日次で rsync、という具合で、いちいち設定が面倒な上に、リアルタイム製にも欠けてしまいます。
そういう問題を解決する上で fluentd はすごく便利そうなので、今年はアレコレ調べたり contribute していければと思っている次第です。
それでは皆様今年もよろしくお願い致します。
2012-12-09 22:54:16
MongoDB で使用する JavaScript 関数をモジュール化して Nodeunit でユニットテストしよう、という話です。
2012 年 12 月現在の Wikipedia によると MongoDB は CommonJS の処理系ということになっているようですが、CommonJS Spec Wiki にその名は見当たりません。
CouchDB は入ってるんですが。
MongoDB を CommonJS 処理系として見たとき、まず一番に辛いのが require 関数が無いことです。
(そもそも require が CommonJS の仕様において必須なのかどうかとかはよく知らない)
require が無くて何が困るかというと、MapReduce に使う関数なんかをモジュール化して書けないので、ユニットテストを書くのが困難、ということです。
そこで、MongoDB において JavaScript コードのモジュール化を促すものを作ってみました。
yuya-takeyama/mongo_require.js
MapReduce に使用するモジュールを作成する
mongo_require.js を使えば、CommonJS と同様のやり方で JavaScript モジュールを作成し、それを MongoDB 内で使用することができます。
それでは定番の WordCount をやってみましょう。
CommonJS のやり方に従って、exports オブジェクトのプロパティとして mapper と reducer の関数をそれぞれセットしています。
ちなみに、ここでは使用していませんが module.exports も使用できます。
これをモジュールとして読み込んで、実際に MapReduce を実行してみましょう。
まず、mongo_require.js を適当な場所に保存します。
そして、MapReduce を実行するのは以下のスクリプト。
はじめに mongo_require.js を読み込むことで、mongo_require() 関数を有効にします。
mongo_require() 関数は CommonJS の require 関数とほぼ同様に使用できます。
ここでの mr オブジェクトにはプロパティとして mapper と reducer それぞれの関数を持っているので、それをそのまま mapReduce 関数に渡しています。
実際に実行すると、以下のように正しく単語数が集計できていることがわかります。
ここではあらかじめ texts コレクションに文が入っていたものとしています。
このように、関数をモジュール化した上で、それを利用しての MapReduce ができました。
モジュールをユニットテストする
関数をモジュール化できたのであれば、それをユニットテストするのも容易になります。
ユニットテストには Node.js など、別の CommonJS 処理系の、既存のテスティングフレームワークを使用することができます。
フレームワークは何でもいいのですが、とりあえずシンプルなもの、ということで今回は Nodeunit を使用してみます。
個人的に Node.js でモジュールを書くときは Jasmine で BDD スタイルに書くのが好きですが、今回のようなシンプルなモジュールであれば、Nodeunit ぐらい素朴なフレームワークの方が向いているのではないでしょうか。
ここでのモジュールの読み込みには普通に require() 関数を使用します。
これを実行すると以下のような結果が得られます。
このユニットテストのポイントとしては以下が上げられます。
- Mapper 関数内で使用する emit() の大体となる関数をグローバル空間に定義している
- Mapper 関数をテストするアサーション関数として mapperEmits() 関数を定義している
mapperEmits() 関数は第一引数として Mapper 関数、第二引数として処理するレコードを受け取り、そのレコードを処理したときに emit() されるレコードが第三引数と一致するかをチェックします。
(emit() と mapperEmits() については npm でパッケージ化することを考えています。)
ところで、グローバル関数に依存したテストを書くことは、本来であればアンチパターンとされています。
例えば大規模なプロジェクトにおいてはグローバル関数は極力避けることが望ましいのですが、ここではひとつの MapReduce をひとつの小規模なプロジェクトとして考えています。
プロジェクトの規模が小さければ、グローバル空間汚染によるコンテキストの複雑化もそれほどは問題にならないでしょう。
Reducer 関数については、通常は入力値を元に値を返すだけの参照等価な関数なので、特にこういった特別な関数を用意せずとも deepEquals() 関数でアサーションができます。
まとめ
mongo_require.js を使って MongoDB で使用する関数をモジュール化する方法と、そのモジュールを Nodeunit でユニットテストする方法について紹介しました。
MapReduce は元々 Mapper と Reducer というシンプルな二つの関数の組み合わせで大規模な計算を行う、というアイディアのもとに考えられています。
このシンプルな関数さえ正しく動作することが保証できれば、多少複雑な集計も安心して実装することができますね。ハピラキ。