nodeでWebアプリを開発するときに考えること

僕は、全体を俯瞰して作り出すので遅いのだけれども、トータルとして同じくらいのスピードになるかなと思って、新しい言語やフレームワークを使うときは、まずは全体で必要になりそうな技術であったり、問題点を潰すことにしています。

フレームワーク

nodeのフレームワークは、たくさんありすぎてどれを使っていいのやら、という状況だけれども express 使っておけば良いというのが僕の中での結論。 ルーティング、テンプレート、セッション管理といった基本的な機能を簡単に使える(connectが素晴らしいというのもあるけど)ので、複雑なことをしたければあとは自分で頑張るほうがいいと思う。

それに、全部読みきれる量であるということ。 使うライブラリのコードを読みきれるというのはすごい重要。フレームワークでサポートしていないことをしたくなったときにどこに手を入れて良いのかがすぐに判断できるから。 また、expressのテストコードはmochaを使っているので、本体と合わせて読むと、どうやってテストコード書けばいいかも勉強できて良い。

O/Rマッパー

RDBMSを使ってそこそこの規模のWebアプリを書こうと思うと、O/Rマッパーがあるとすっきりする。が、探してみるとActiveRecordのような決定的なものがない。 ぱっと見でよさそうなのが、node-orm2

https://github.com/dresende/node-orm2

基本的なクエリに加えて集計関数も投げられるし、has-oneとかhas-manyといった関連も定義でき、フックも充実、さらにexpressとも相性がいい。 Rails界に住んでる人は、モデル毎にファイルを分けて定義したくなるので、手動でロードしないといけないのでちょっと面倒。

var orm = require('orm'),
    fs = require('fs');

var dbConfig = JSON.parse(fs.readFileSync(__dirname + '/database.json'));
var db = orm.connect(dbConfig.dev);
fs.readdir(__dirname + "/models", function(err, models) {
  if (err) {
    console.log(err);
    return;
  }
  models.forEach(function(model, i) {
    db.load(model, function(err) {
      console.log(err);
    });
  });
});

一般的にnodeでRDBMSを操作することはやはり少ないのかなぁ。スロークエリ頻発するともはや非同期IOは意味をなさなくなるというところから、mongoやCouch、Redisを積極的に使う方針にしたほうがよいのだろうか。

migration

RDBMS使うなら、migrationの機構がないと死ねる。node-db-migrateが使いやすい。Rails流。up/downで操作、データベースにmigrationsテーブル作って管理するところなど。

https://github.com/nearinfinity/node-db-migrate

上記のnode-orm2と相性はまぁまぁ。唯一ぐんにょり来るのが、設定ファイルのプロパティ名が違っていること。 node-orm2では、protocolだがnode-db-migrateはdriverになっている。database.jsonで両方記述しておくとうまくいくが、ぐんにょり来る。 あと、Rails界の人は、

  • updated_atcreated_atは必ず自分で定義しないといけない
  • サロゲートキーも自分で定義(idも自動付与されず自分でPK、AUTO_INCREMENTを指定する)

に注意。

auto loader

hotnodeが今のところ快適に使えている。 コヒスク使う人は、hotcoffeeコマンドで。 シングルページアプリケーション程度の規模なら問題なし。

https://github.com/saschagehlich/hotnode

デバッガ

とりあえず、node-inspector使っておけ、らしい。

https://github.com/dannycoates/node-inspector

Webkitブラウザ上でサーバサイドのコードをデバッグ実行できるので死ぬほど便利。一度使うと便利すぎてないと生きていけなくなる。 上記のhotnodeとも組み合わせて使える。

$ hotnode --debug app.js # アプリ起動
$ node-inspector # 別ターミナルで実行してChromeなどにアクセスする

ホスティング

普通のWebアプリなら迷わずHerokuだけれども、たとえばWebSocketを使いたい場合Herokuはまだサポートしていない。

https://devcenter.heroku.com/articles/using-socket-io-with-node-js-on-heroku

Herokuは1Dynoなら無料で公開できる(ただし750時間までなので注意)ので、これほどまでに使いやすいPaaSはないので諦めるのは辛いところ。 WebSocketの代わりにロングポーリングでも良いのだけれども、iPhoneiPadだと通信中のぐるぐるが回ったままで、気にする人は気にするので気持ち悪さを与えて結果として使わないということがあるので、なるべくWebSocket使いたい。

とりあえずは、Herokuで作り問題が出て来て(お金に余裕のある人は)、nodejitsuやAWS、VPSあたりを借りるがいいのでは。

デプロイ

nodeアプリを動かすときに、Heroku や nodejitsu といったPaaS使わない場合、手動(capコマンド実行等)/自動(git pushトリガー, jenkinsトリガー)問わずproduction環境にデプロイする必要がある。

Stack Overflowの解答を眺めていると、Capistranoを使っている人が割と多い。

プロセス管理

nodeは、内部でエラーが発生すると、落ちる。イコールサービスが止まる。なので、基本的に自動で上げるためのプロセス管理が必要になる。 まぁこれはいくらでもあるので、monit、god、supervisor等慣れたものを使うのが良いと思う。

あとは、node製のものとして、foreverがある。 nodejitsuはforever使っているとのこと。 Stack Overflowでの解答も「とりあえずforever」という意見が多い。

Rubyの標準ライブラリ及びgemのtagsファイルをロードする

Vimスクリプトを書いた。 https://github.com/yoppi/config/blob/master/vim/dot.vim/ftplugin/ruby/tag.vim

プログラミングしているときに、実装を知りたいときがよくあるので、Vimで素早くタグジャンプできるようにした。 ただ、

という問題があるので、もっと便利なようにtagsファイルを動的に更新していくような感じにしていく。

redisable

というgemを作った。

http://github.com/yoppi/redisable

RubyオブジェクトをRedisとマッピングさせる既存の有名なライブラリに、

の2つがあるけど、次の欲しかったものを実現するのが少し遠かったので作った。

  • モデル毎に複数のRedisサーバにアクセスできる(sharding)
  • オブジェクトをマッピングするというより、そのオブジェクトを通してRedisにアクセスしたい

という意味を込めてredisable(redis + enable)という名前にしている。

RSpecMocks/rr

TestDoubleライブラリをrr(Double Ruby)からRSpec標準のRSpecMocksに切り替えてるのだけど、やっぱり違和感がある。 rrはTestDoubleの考えをそのままライブラリ化した実装で、xUnit Test Patternsを熟読した人にとって馴染み易いと思う。

だけど、RSpec Mocksはいまいちstubmockの使い分けが不透明だったり...mockspyに当たるのはshould_receiveを使うというのがわかりづらいなーと。 rr、全然メンテナンスされてないのでメンテナになろうか、と、思ったら、

https://github.com/rr/rr

メンテナの人が変わってて活発にコミットが入る様になった模様。これで一安心か...

redis-commander

ローカルで開発しているときに、複数のredis-serverを立ち上げるので、たとえばsessionのテストでkeyを削除したくなったり、 アプリのデータを入れたくなったりしたときに、redis-cliで「どのプロセスに繋げばいいだっけ」ということが多々ある。

redis-commanderはブラウザで経由でredis-serverを管理してくれる。 http://nearinfinity.github.io/redis-commander/

$ npm install -g redis-commander
$ redis-commander

便利。

thinのコードネーム

が、rails serverするたびに目に入るので気になって一覧にしてみた。

$ git log -p lib/thin/version.rb | grep '\+\s*CODENAME' | awk 'match($0,/[\047"](.*)[\047"]/) { print substr($0,RSTART,RLENGTH) }'
"Straight Razor"
"Knife"
"Chromeo"
"Low-bar Squat"
"Triple Espresso"
"Double Espresso"
"Bat-Shit Crazy"
"I'm dumb"
"Black Keys Extra Plus Wow"
"Black Keys"
"Does It Offend You, Yeah?"
"No Hup"
"Crazy Delicious"
"This Is Not A Web Server"
"Flaming Astroboy"
"Astroboy"
"I Find Your Lack of Sauce Disturbing"
"Asynctilicious Ultra Supreme"
"Asynctilicious Supreme"
"Super Disco Power Plus"
"Super Disco Power"
"?"
"That's What She Said"
'The Big'
'Double Margarita'
'Rebel Porpoise'
'Dodgy Dentist'
'No Name Yet'
'Fancy Pants'
'Spherical Cow'
'Ninja Cookie'
'Rambo'
'Cheesecake'
'Big Pony'
'Bionic Pickle'
'Pony'
'Flying Mustard'
'Purple Yogurt'
'Cheezburger'
'LOLCAT'
'lolcat'

awkでシングルクォートをマッチさせるのが死にそうになった。

Amazonで配送先住所を間違って発注してしまった場合

def 住所変更可能か?
  状況 == "未発送"
end

def 配送先住所をwebから変更する(住所)
  変更する 住所
end

def 配達センターに電話する(配送会社電話番号, 伝票番号, 住所)
  電話する 配送会社電話番号, 伝票番号, 住所, "「配送先を間違えたので住所変更お願いします」"
end

def main
  if 住所変更可能か?
    begin
      配送先住所をwebから変更する(住所)
    rescue => 変更不可エラー
      # 落ち着く
      配達センターに電話する(配送会社の電話番号と伝票番号をメールから調べる, 住所)
    end
  else
    配達センターに電話する(配送会社の電話番号と伝票番号をメールから調べる, 住所)
  end
end