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でお仕事もしているらしいし。