Scala + finagle + MongoDB + Herokuで2chクローンを作る(連載第2回)
タイトルにfinagleを加えて第2回。
Scala勉強会(56回)に参加したら、@xuwei_k さんがcasbahのチュートリアルセッションをしてくれた。いやー、前回のブログは全く有益な情報はないのにもかかわらず、書いておいてよかった。アウトプット重要。
casbahはJava版ドライバの薄いラッパであること。MongoDB -> Java、またはJava -> Scalaでそれぞれインピーダンスミスマッチがあることを教えてもらった。あと、実際に人が開発している様子を見れたのは貴重。コマンドラインでの実行方法とか、ちょっとしたデバッグの方法とか。こういうのは、勉強会に参加する大きなメリット。
Casbahを使ってScalaでデータを入れてみるか。ますはCasbahのインストールドキュメントを読む。むむむ、インストール方法の選択肢が5つもある。それぞれのメリット・デメリットがよくわからないなぁ… ScalaからMongoDBへアクセスする – Casbah編 ではsbtというのを使っていたので、それ使ってみよう。
sbtとはなんぞや。コップ本の巻末索引を見るとあった。どうやらビルドツールらしい。コップ本には基本的にsbtの解説がない。
sbtのページにある手順 を参考にして、導入。またインストール方法の選択肢がいっぱいあるのかと思いきや、jar配置して、起動スクリプトを書くだけでいいらしいので楽チン…。かと思いきや、sbtを実行するとエラーが出まくる。
Error: Could not retrieve JNA
どうやら依存しているJNAの自動フェッチに失敗しているらしい。
~/.ivy2/local/net.java.dev.jna/jna/3.2.3/jars/ にjna-3.2.3.jarをjna.jarにリネームして置いてあげたら、
Error: Could not retrieve Scala 2.9.1i
だって。
ここでふて寝しそうになったが、Debianで動かすのをやめてMacで動かしてみる。最近のオシャレミドルウェアは大体Macで簡単に入るようになっている。そうでないと開発者に使ってもらえないからねー。マーケティング的に。
というわけで、Macで動かしたらあっさりsbt動いた。sbt起動時のjavaのオプションに、-Dfile.encoding=UTF-8もつけるようにしておく。
mongodb+casbah+scalaのページ を参考に、build.sbtを書く。
Antはずっと前に触ったことあるけど、MavenとかIvyとか全然知らんかった僕。とりあえず、プロジェクトファイルを書けば、プロジェクトの生成と依存ライブラリの導入ができるらしい。
build.sbtってファイルはこんな風に書けばいいのかな?
name := "Clone2ch" version := "1.0" scalaVersion := "2.9.1" libraryDependencies ++= Seq( "com.mongodb.casbah" % "casbah_2.9.0-1" % "2.1.5.0" )
試しにsbt起動したらエラー。eof expected but ‘;’ found.だと。 どうやら、各行ごとに空行を挟む必要があるみたい。空行挟んだら通った。 どういうパースをしているのかいまいちわかってない。
> mkdir -p src/main/scala > vim src/main/scala/TestMongo.scala
object TestMongo { def main(args: Array[String]) { println("test mongo") } }
んで、sbt runとかすると、「test mongo」って表示される。ふー、長かったぜ。初めての対話環境外でのScalaプログラミング。んじゃ、さっそくMongoDBに値を突っ込んでみよー。
import com.mongodb.casbah.Imports._ object TestMongo { def main(args: Array[String]) { val connection = MongoConnection() val db = connection("clone2ch") val collection = db("bbs") collection += MongoDBObject( "name" -> "lobby", "name_jp" -> "ロビー", "domain" -> "mentai.2ch.net", ) collection.find().foreach(println) } }
エラー。どうやら、MongoDBObjectのファクトリメソッドに渡している最後のコンマがまずいらしい。取ってみる。…動かない。import com.mongodbの時点で、comにmongodbなんてメンバないぜと言われる。
しばらく悩んだ結果、pwdがsrc/main/scalaだったのがダメみたい。sbtが見るbuild.sbtはカレントにないとダメなのね。Hello, World的なものの場合には何もimportしていなかったから、カレントにbuild.sbtがなくてもsbt runが問題なく動作していたらしい。build.sbtがあるディレクトリまで上がって実行。
"_id" : { "$oid" : "4e9c5f8e03644d5841503e66"} , "name" : "lobby" , "name_jp" : "ロビー" , "domain" : "mentai.2ch.net"}
おおー、動いたきゃっきゃ。もっかいrun!
{ "_id" : { "$oid" : "4e9c5f8e03644d5841503e66"} , "name" : "lobby" , "name_jp" : "ロビー" , "domain" : "mentai.2ch.net"} { "_id" : { "$oid" : "4e9c5feb036461f9a18ebaad"} , "name" : "lobby" , "name_jp" : "ロビー" , "domain" : "mentai.2ch.net"}
ぎゃーレコードが2つに増えた。nameをidにすればいいのかな?でもnatural keyとsurrogate keyは分けたいよね。$oidはどうやら一意でかつ必ず付与されそうだから、natural keyをそのままidとしても問題ないのかな?と思ったけど、nameをid_とするのはやめておこう。
unique制約的なものはどうやって付与するんだろう。
「mongodb unique constraint」でGoogle検索。MongoDBのJavaScriptでは
db.bbs.ensureIndex({name: 1}, {unique: true});
的な感じでunique制約が付けられるようだ。
「ensureIndex casbah」でGoogle検索。
def ensureIndex [A] (keys: A, name: String, unique: Boolean)(implicit arg0: (A) ⇒ DBObject): Unit
でuniqueインデックスが付けられるらしい。
ふと思ったこと。シャーディング時のunique制約確認はどうやっているんだろう。まあ、bbsはシャーディングするほどのレコード量にならないからいいか。
SQL to Mongo Mapping Chartが便利。あと、これも勉強会で教えてもらったのだが、sbt consoleが便利。sbtのプロジェクト環境のもとで、対話的な実行ができる。
さて、実際にモデリングを始めてみよう。
2ちゃんねる掲示板の要件を簡単に整理。
- 掲示板は、ホスト名と掲示板名で一意に特定される。
- スレッドは、掲示板とスレッドIDで一意に特定される。
- レスは、スレッドとレス番号で一意に特定される。
be板も考えると、
- ユーザは、メールアドレスで一意に特定される。
- レスには、ユーザが1対1対応する、もしくはユーザが空である。
というわけで、基本的に1対多なリレーションが多いわけですな。
MongoDBにおける関連(Relation)のスキーマ設計 を読むと、1つのCollectionになるべく詰めてあげたほうが効率がよいらしい。しかし、Collectionには最大サイズがあるため、実用的にはCollectionを分割するようだ。今回は、それぞれのCollectionを別に分けて設計したい。
しかし、Scalaらしいオブジェクトモデリングがわからん。素直にBbs、BBSThread、Res、Userとかをcase classで書き起こせばいいんだろうか。また、日付型は何を使うのがいいのかな?…これは勉強不足だな。というわけで、Scalaで書かれたソースコードを読むお勉強モードに切り替える。
finagleを使うのであれば、finagleのサンプルコードをついでに見るのがよいだろう。Google検索の結果により、Hello fingleという素敵なプロジェクトを参考にすることにする。
今日はScalaハッカソンに参加中。GMOのカフェは居心地がとてもよい。白熱電球色でありながら、光量が確保してあって、音響もちょうどいい。あと、クリスタルのドラえもんブックエンドがある。GMOすばらしい。多謝。Scalaでお仕事もしているらしいし。