JSON on HTTPやWeb APIを各言語でどうやって実装するのか
HTTPでアクセスして、JSONを返すようなWebサーバを書きたいとする。 どんな言語を選ぶか。どんなミドルウェアを選ぶか。どんなライブラリを選ぶか。
たとえば、TIOBE Softwareが公表している「Programming Community Index(PCI)」という指標がある。人気のあるプログラミング言語の数値化。これを見ていて思ったのは、「多すぎだよね、プログラミング言語」ということ。これらのうち、どの言語を勉強し、どの言語をプロジェクトに採用すべきなのか。
その感触を得るために、 「同じ仕様のREST serviceを複数言語で実装したらいいんじゃね?」 と思った。いくつかの言語で実装を起こしてみている。
前提条件
大規模な開発を想定する。ユーザの規模が大規模。トランザクション数が大規模。そして、開発者が大規模。
実用的かつモダンな開発を想定する。プロジェクト毎のバージョンを固定したモジュール管理、コード埋め込みドキュメント、ユニットテスト、継続的インテグレーション、A/Bテスト、ロギング、死活監視、プロファイリング、エラー時のわかりやすいスタックトレース、ホットデプロイ、セキュリティ、認証、ランキング、memcachedでのキャッシュ、Accept-Languageによる文言の多言語化などである。
Web APIの仕様は、RESTful API Design Second Editionを参考にする。HATEOASな設計にそれほどこだわらずに。
要件
言語は、以下の3つを当初のターゲットとする。
- Perl
- Ruby
- Java
以下のような要件とする。よくあるWebシステムで、API提供寄り、というイメージ。
- JSON via HTTP 詳細な仕様はGitHub Pagesを参照のこと。
- かんたんなCRUD。
- データベースのパスワードなど各種設定は、YAMLから読み込む
- 複数人での開発を前提とする。RouterとかDispatcherのような、HTTP path to methodのマッピングは分割可能とする。
- RDBMSはMySQL。余計な論点を持ち込まないため。
- 依存パッケージはプロジェクトローカルに配置し、インターネットから自動fetchする
- かんたんなHTMLをテンプレートを通じて返すことができる。ちょっとした動的HTMLを返したいこともある。
- 静的なファイルをHTTP経由で配信できる。
- APIサーバ用途がメインだが、ちょっとした動的HTMLを返したいこともあるだろう。
- O/Rマッパーは使わないかわりに、SQL builderを使う。
- HTTP RequestのAcceptヘッダの先頭を見て、HTMLの場合にはJSONをインデントつけて見やすくしたHTMLとして出力してほしい
- エラーの場合、HTTP RequestのAcceptヘッダの先頭を見て、HTMLでない場合にはJSONのフォーマット内にリッチなエラー情報を埋め込んで欲しい
ドキュメントはSphinxで書く。RESTfulなAPIの仕様を書くために、Sphinx HTTP Domainを導入。
以下、各言語で未完成ながらもミドルウェア選定・実装をしてみた感想を述べる。
Ruby
まず、Rubyから実装してみた。テストはほぼ未着手。
選んだミドルウェア・ライブラリはこちら。
Unicorn/Sinatra/mysql2/Arel/yaml/json/YARD yard-sinatra/rspec rake-test/memcached/RubyProf/shotgun/rack-protection/split/rvm gem
SQL BuilderであるはArelはActiveRecordの一部となっている。よって、最小限のActiveRecordのメソッドも使う。
ついでに、Travis CIで継続的インテグレーションを行う。
Sinatraのかわりに、grapeとrestfulieの採用も考えたけど、周辺のプロダクトやドキュメントの充実っぷり、HTMLでのスタックトレースのキレイさに惹かれたんよ…。
Perl
Perlでは、簡単なプロトタイプ実装をしてみた。仕様をフル実装はできていないし、Dispatcherも分割できていない。
選んだミドルウェア・ライブラリはこちら。自信ないなー。
Starlet/Amon2/DBD::mysql/SQL::Maker/YAML::XS/JSON::XS/POD/Test::Unit Test::WWW::Mechanize::PSGI/Cache::Memcached::Fast/Devel::NYTProf/Plack::Loader::Shotgun/Amon2::Plugin::Web::CSRFDefender/(A/B testing framework見つからず)/cpanm
Perlで一番いやだったのは、JSONで「数値」を返すために加工が必要なこと。MySQL由来の「数値」は、明示的な加工をしない限り「数字」になってしまう。JSON Schema的なものを使えばいいんだろうけど。
WAFについて、Mojolicious::LiteかAmon2か悩んだ。Mojolicious::Liteのほうが見た目的にシャレオツな書き方ができる気がする。特にフック部分。あと、コップの水が溢れるエラー画面がカッチョイイ。国内Perl Mongersへの刺さり具合を考えてAmon2とした。
PSGIベースのA/Bテストフレームワークはあるのかな。Rubyのsplit的な。
あとは、Doxygen的なものをPODでやるのはどうするんだろう。YARDでPerlのドキュメントを書く、ってものもあったけどアクティブじゃない。
Java
Javaのコードは久しく書いていない。知識レベルが、JDK 1.3で止まっている。 そのせいもあり、Javaのミドルウェア選定は悩んでいて、プロトタイプ実装しかできていない。
選んだミドルウェア・ライブラリはこちら。自信ゼロ。
Jetty/htmleasy(resteasy)/ConnectorJ/Querydsl/SnakeYAML/JSON.simple/JavaDoc/TestNG Spock/xmemcached/(JVMTI対応の何かのプロファイラ)/Gradle Jetty Plguin/(Servlet用のセキュリティフィルター探してない)/(A/B test framweworkも探してない)/Gradle Ivy
Sinatraライクなフレームワークは、Spark/Napalm/htmleasyの3つを発見。 htmleasyは、JBossのRESTEasyというJAX-RS実装を元にしているようだ。 3フレームワークの中では、一番開発が活発そうだ。これにしよう。
そもそも、JAX-RSという仕様があるのを知らなかった。なんでも仕様があるなJava。リファレンス実装はJersey。Apache CXFなんて実装もある。
SQL builderはjOOQ、Querydsl、iciqlで悩んだ。QuerydslはSpring JPAやSpring Data JDBC Extensionでもサポートされている。よって、Querydslを選択した。
問題なのはJDBCラッパ。まずは、生JDBC + Querydslを検討し、プロトタイプ実装をしてみた。次に、Spring JPA + Querydsl、Spring Data JDBC Extension + Querydslも考えた。いまいちどれがいいのかわからないし、htmleasy(RESTeasy)と食いあわせがよいものがわからない。
ビルドツールはGradle。sbtと迷った。Scala版を書くならsbtなので、JvaはGradleでいくことに。YAML読み込みやQuerydslのモデルクラス作成も頑張って書いてみた。Maven Pluginで用意されている機能を移植するのはめんどいなー。Gradleでのライブラリ読み込みがいまいちよくわかっていない。今どきのJavaっ子はGradleだよねーというのにほだされた。実際、Gradleのほうが小回りが利いてよい。
DI containerはGuice。XML書かなくてよいという噂だけで選択。んでも、DI containerのキモをつかめてないんだよなー。モデルクラスをPOJOにしたいでしょ?ってくらいの認識しかないんだなこれが。
JRuby + Netty
これは飛び道具的な。ちょっとネタ的だが、意外といけるんじゃねと思っていたりする。とりあえず、HTTPで固定のレスポンスを返すまで実装してみた。
Nettyとは、非同期I/Oネットワークアプリケーションフレームワーク。こないだまでJBoss内のプロジェクトだった気がしたけど、どうやら独立したらしい。
ちなみに、Nettyを使ったRack対応サーバとして、aspenというものがある。
Python / PHP / Scala / C# / JavaScript
未着手だが、これらの言語でも実装したい。
Pythonはさほど問題なくミドルウェアの選定も開発もできそう。PHPはRESTなんてガン無視しつつ生PHPで書くが好みだが、かなりミドルウェア選定で悩みそう。Scalaは2ch cloneの開発が止まっているが、そちらを先にやりたい。C#は、MS系に寄せるか、そうでないかで選択肢がありそう。JavaScriptだとNodeを使うんだろうが、どのくらいミドルウェア揃っているんだろう。
TODO
まだまだやるべきことは多い。俺TDD苦手だな。テストがあることが重要なのではなく、コードがテスタブルであることは重要だ、という信念は持っている。しかし、やはりテストを書くのはめんどい。
- 各言語で仕様を満たす。
- テストカバレッジ100%を目指す。
- ドキュメントカバレッジ100%を目指す。
- OAuth2認証
- memcachedでGETの場合のキャッシュを行う。
- 日次バッチ
- A/B test
- ランキング
- fluentdによるロギング
- 死活監視、リソース監視
各言語でスループットの計測も行いたい。早ければ正義というわけでもないが、生産性と性能とのトレードオフを見極めたい。TPC-C/Eとまでは言わないが、何かしらのJSON/HTML on HTTPの標準的な性能評価法が確立していて欲しい。
現時点の僕の実装は、各言語とも完成度が低い。同じハードウェアで計測する場合でも、OSの設定や、JVMの設定、コンパイルオプションも性能に大きく影響する。それらの数値をチューニングした上で計測を行う必要がある。適切なチューニングを行わないままベンチマークを行い、その結果をブログに掲載したらどうなるか。想像するだに身震いする。モヒカンの宴。abかhttperfあたりでの性能計測シェルスクリプトを書くくらいに留めておこう。ヘタレ俺。
まとめ
結局、どの言語・ミドルウェア・ライブラリが適しているかの結論は出せていない。そして、将来的にも出せる見込みはない。ただ、各言語でこのプロジェクトが目指すところを実装することには意味を感じている。実用的なWeb APIプログラムを書くときの基礎はあるべきだ。
今回のプロジェクトは、メジャーな3言語を対象とした。情報があふれている3言語なのに、調査にかなり時間を費やした。情報があふれているからこそ、調査に時間がかかるのかもしれない。
なぜJavaの調査をしたか。これからの大規模サーバサイドWeb開発環境は、いよいよJVM上に寄っていくのではないか、と予想しているからである。
バックエンドのロジック部分と、フロントのビュー作成部分は分けたい。お互いを呼び出す必要がある。 内部APIを呼び出しするだけなのに、HTTP(あるいはThriftでも)のオーバーヘッドに耐えなければならない。それはつらい。すべてJVM内部で閉じていれば、関数呼び出しで済むのだ。
仮にJVM外の言語から機能を利用しようとすれば、それこそHTTP経由でアクセスすればよい。HTTPクライアントライブラリがないWeb向け実用言語はないだろう。HTTPさえサポートすれば、最低限の相互運用性は確保できる。たとえば、全銀協標準プロトコル@TCP/IPはCORBAを採用しているが、Web系に限れば、もはやCORBAもSOAPも出る幕はないだろう。
バックエンドのロジック部分は静的型付言語のほうがいいのではないか。動的型付言語は使い物にならないという意見もあるが、そこまでは思っていない。しかし、純粋なロジック部分については静的型付言語で書きたい。
フロントのビュー作成部分は、やはりゆるく・スピーディーに書きたい。また、教育コストが低くあって欲しい。そして、「楽しく」あってほしい。
JVM言語でメジャーなのは、Java/Groovy/Jython/JRuby/Clojure/Scala/Kotlinか(JPerlは名前的に紛らわしいな…)。それはともかく、これらの言語のどれを採用すべきか。
個人的には、JRuby on RailsとScalaの組み合わせなんかが面白いと思っている。Ruby on Railsはちまたに解説も大量にある。少しのお膳立てをしてあげれば、JRuby on Railsも難しくない。LinkedInの事例もあるよね。TwitterはJavaとRubyをThriftでつないでいるから、これはまた違った路線。
しかし、各言語で知らなきゃいけないことが多すぎないか。本来は3言語で仕様を完璧に満たして公開したかったが、途中公開に踏み切ったのは、そもそものミドルウェア・ライブラリ選定に早めにツッコミもらったほうがよさそうだ、という判断による。JSONパースライブラリが複数あってどれ選べばいいかわからないという状況は、「多様性があっていいよね」とプラスに捉えられるほど僕の人生は余っていない。
「なんでこの言語がないんだ!」という方々。 「こんなミドルウェア・ライブラリ選ぶなんてセンスねーな」という方々、 pull requestお待ちしております。マジで。書いてて自信ないんで。
スペシャルサンクスで、同僚の稲川さんと、勤め先のハッカソン部(部長 id:riywo)、java-ja温泉参加者のみなさま。はてブで意見いただいた方もありがとうございます。たしかに話を聞いているとPlay! 2は合致してそうなんだよなー…
Make JDBC metadata class for Querydsl on Gradle with settings written by YAML
This is an example of build.gradle to make JDBC metadata class for Querydsl.
JDBC settings are written in ../config.yaml. The target RDBMS of JDBC is MySQL.
It’s not smart, although it works well.
// vim: set expandtab ts=2 sw=2 nowrap ft=groovy ff=unix : */
sourceCompatibility = 1.6 // TODO: 1.7
version = '1.0'
group = 'jp.co.wktk.apiserver'
apply plugin: 'java'
apply plugin: 'jetty'
jettyRun.contextPath = ''
jettyRunWar.contextPath = ''
task wrapper(type: Wrapper) {
gradleVersion = '1.0-milestone-8a'
}
import groovy.sql.Sql
import com.mysema.query.sql.MetaDataExporter
sourceSets {
main {
java {
srcDir getGeneratedSrcPath()
}
}
}
// This build script uses snakeyaml for load YAML settings about database for Querydsl.
import org.yaml.snakeyaml.Yaml
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath group: 'org.yaml', name: 'snakeyaml', version: '1.10'
classpath group: 'com.mysema.querydsl', name: 'querydsl-sql', version: '2.3.1'
}
}
repositories {
mavenCentral()
maven {
url 'http://source.mysema.com/maven2/releases/' // for Querydsl
}
maven {
url 'http://mvnrepository.com/' // for MySQL
}
}
configurations {
mysqlDriver
}
dependencies {
compile(
// for querydsl
[group: 'com.mysema.querydsl', name: 'querydsl-sql', version: '2.3.1'],
[group: 'org.slf4j', name: 'slf4j-log4j12', version: '1.6.1'],
// for yaml
[group: 'org.yaml', name: 'snakeyaml', version: '1.10']
)
testCompile(
[group: 'org.testng', name: 'testng', version: '6.4']
)
runtime(
// for MySQL
[group: 'mysql', name: 'mysql-connector-java', version: '5.1.18']
)
mysqlDriver group: 'mysql', name: 'mysql-connector-java', version: '5.1.18' // for MySQL
}
compileJava {
doFirst {
// load config from YAML
File configFile = new File('../config.yaml')
Yaml yaml = new Yaml();
def config = yaml.load(configFile.newReader())
def dbConfig = config.database;
// Load MySQL Driver
configurations.mysqlDriver.each {file ->
gradle.class.classLoader.addURL(file.toURI().toURL())
}
// Create classes for Querydsl
def sql = Sql.newInstance("jdbc:mysql://${dbConfig.host}:${dbConfig.port}/apiserver",
dbConfig.username,
dbConfig.password,
'com.mysql.jdbc.Driver')
MetaDataExporter exporter = new MetaDataExporter();
exporter.setPackageName('jp.co.wktk.apiserver.persistence')
exporter.setTargetFolder(getGeneratedSrcPath())
exporter.export(sql.getConnection().getMetaData())
}
}
test {
useTestNG()
}
def getGeneratedSrcPath() {
new File(
buildDir.absolutePath + File.separator +
'generated-src' + File.separator +
'java')
}
第1回ニコニコ学会βでダダすべり発表してきました
第1回ニコニコ学会βという学会で発表させていただきました。運営の皆様がた、発表の機会を設けてくださり誠にありがとうございます。職場の皆様がた、忙しいさなか有給使ってごめんなさい。
ニコニコ動画そのもののデータ研究をやっている人が少ないこと、またデータ研究は3分間での解説が難しいこと、この2点に悩んだ結果、「ともかくウケ狙い」といういつもの路線に落ち着いた次第です。当初の予定には入っていた、マック赤坂ばりに「みなさ〜ん…”ニコニコっ”…してますかぁ〜」というネタは、やらなくてよかったと今となっては心底思っています。
学会で紹介した、ニコニコ動画タグの関係性を表した生データ(重みなしグラフ、lgl形式)と、それをデンドログラム・Voronoi Treemapで図示したpdfをダウンロードできるようにしました。

発表当日いい忘れたことがあります。やはり、ニコニコ動画は「音楽サイト」だな、ということです。
- 商用音楽
- 同人音楽、ボーカロイド(打ち込み文化)
- 歌ってみた、踊ってみた(音楽はあくまでネタのひとつ、みんなが知っている曲が対象)
が大きな3ジャンルを占めています。ドワンゴはもともと着メロサイトをやっていたこともあり、やはり音楽コンテンツが強い会社、ということなのでしょうか。
また、東方・アイマス・Vocaloidのいわゆる御三家は、「部品化」されているという印象を持っています。これらは、ニコニコ動画でコンテンツを作る際の欠かせないミドルウェアです。ゲーム・音楽・キャラクターの3大要素を兼ね備えた、新しい御三家の登場を期待します。
Scala + finagle + MongoDB + Herokuで2chクローンを作る(連載第3回)
11/3に、@xuwei_k さんの誕生祝 & Scalaハッカソン!に参加した。とりあえず、Hello Finagleを見ながら、Finagle製のWebサーバを立てることを目指す。まずは、静的なHTMLファイル返答するところまで作ろう。
むむむ…sbtの標準ディレクトリ構成がわからん。sbtのディレクトリ構成はMaven由来らしいので、Mavenのディレクトリ構成を見る。
どうやら、src/main/webappというディレクトリを掘ればいいらしい。ホントにこれでいいのかな。「sbt resources 」でGoogle検索。そのものずばりな、What are “resources” folders in SBT projects for?という質問を発見。そこのリンクから、さらにhow to get a resource within scalatest w/ sbtを見る。
どうやら、リソースファイルのパスprefixはgetClass.getResource()で取得するのがJavaでは普通で、Scalaでもそうするらしい。Javaに詳しくないことがバレてしまう。staticに配信するHTMLファイルはsrc/main/resourcesかsrc/main/webappかどちらに置いたほうがいいのか、よくわからなくなってきたぜ。とりあえずwebappに置くことにする。
finagleを用いたWebサーバのひな形(あきこ)がある。どうやら、Nettyが用意するHTTPハンドラを使っているらしい。このひな形をベースに、
- ^/$, ^/css, ^/js -> staticなファイルを返す、mime-typeは拡張子からマッピングする
- それ以外は、それぞれのcaseクラスにマッピングしてハンドリングする
みたいなことを書いていけばいいだろう。しかも、パスからのdispatchで、matchとか使えばScalaっぽくなるっしょ!?
まずは、HTTP Methodのハンドリングだな。NettyのHTTPRequestのメソッドであるgetMethod()でパターンマッチするのが常道か。「”getMethod match”」でGoogle検索。それでひっかかった、PunkというWebフレームワークがサイズ小さそうなので読んでみる。
ソースを読む上で、vimのsyntax highlightが欲しい。
sbaz install scala-tool-support cp -R /usr/local/Cellar/scala/2.9.1/libexec/misc/scala-tool-support/vim/ ~/.vim
という風にしたら、syntax highlightがついた。いえー。
Punkでは、静的ファイルをどのように読み込み、クライアントに返しているのか。
ack File
でgrep。src/main/scala/punk/PunkFilter.scalaにreadFile()というメソッドがあるらしい。
private def readFile(url: URL) = {
scala.io.Source.fromURL(url).mkString
}
うーむ。scala.io.Source.fromURL(url).mkStringってその場で文字列作るから、ファイルI/O待ってブロックするんじゃねーの?
FinagleのイベントループにFile I/Oも載せられるような何かはないのかなー。初心者のクセに欲張りかもしらんけど。
と思ったらあったー、Finagleのドキュメント内、Using Future Pools。
FinagleはFutureという型をよく使うが、一種の遅延評価みたいなもんだろう。上記のサンプルに、importを補うとこんな感じか。
import com.twitter.finagle.Service
import com.twitter.util.FuturePool
import java.util.concurrent.Executors
class ThriftFileReader extends Service[String, Array[Byte]] {
val diskIoFuturePool = FuturePool(Executors.newFixedThreadPool(4))
def apply(path: String) = {
val blockingOperation = {
scala.Source.fromFile(path) // potential to block
}
// give this blockingOperation to the future pool to execute
diskIoFuturePool(blockingOperation)
// returns immediately while the future pool executes the operation on a different thread
}
}
このThriftFileReaderを実行してみると、型エラーが出た。fromFileはscala.io.BufferedSourceを返すが、FuturePool.apply()は、Array[Byte]を受け取る。scala.io.BufferedSourceからArray[Byte]に変換する方法を調べよう…と思ったけど、面倒なのでやめる。toArrayあたりでいいのかな?
とりあえず、同期でもいいからファイルをHTTP経由で返すところまでもっていこう。
response.setContent(copiedBuffer(scala.io.Source.fromFile("src/main/webapp/index.html").mkString, UTF_8))
うむ。ブラウザからアクセスしたところ、index.htmlが表示された。しかし、このcopiedBufferをはさむところがなんかダサいな。
responseはNettyのHttpResponseのインスタンス。setContentはバッファを受け取る。fromFile()はscala.io.BufferedSourceを返す。setContentが受け取るのはorg.jboss.netty.buffer.ChannelBufferインターフェース互換のもの。うまくやれば直接変換できそうな予感。そうすれば、FuturePoolでラップしなくても、実際にI/Oが必要となったときにFile -> Networkに直に転送してくれたりできそうな気配がする。けど、これもあまり深追いしないでおく。
pathによってきちんと返すファイルを変えよう。
response.setContent(copiedBuffer(scala.io.Source.fromFile("src/main/webapp" + request.getUri).mkString, UTF_8))
動作した。
上記のサンプルは、「/」へのアクセスでindex.htmlを返してくれない。また、ありがちな「../../../etc/passwd」的なものに弱い。前者について、HttpRequestのパスが「/」の場合には「/index.html」と解釈するようにしよう。後者について、パスを作ったあとパスの正規化をして、そのprefixをチェックする、っていうのが一般的な処理だろう。今回は、Scalaの正規表現を勉強したいので、とりあえず「..」という部分文字列があったら例外を投げるようにしてみる。
こんな感じでよかんべ。「..」にマッチする正規表現をソースコードに埋め込むのは面倒だ。最初は、”\\.\\.”と書いたが、”"”\.\.”"”とも書けるようだ。
val tentenRegex = """\.\.""".r
val path = request.getUri
val filePath = "src/main/webapp/" + path match {
case tentenRegex() => throw new Exception
case "/" => "/index.html"
case _ => path
}
初match!どきどき…あれれ、動作しない。ルートにアクセスしても、/index.htmlの内容を返してくれない。なんでだろ?調べてみると、pathが「/」の際に、2個目のcaseにマッチしていないようだ。なんでだ。俺たちはまだ、青春知らずさ。
ためしにifでマッチするかどうか試してみるか。
val path = request.getUri
if (path == "/") {
println("match de-su")
}
match de-suって表示された。ifだとうまくいくようだ。なんでだろう。matchへの理解不足だな。とりあえず、スタンドアロンのmatch検証プログラムを書いて動作させるか。道のりは遠い。
今回の分はコミットしていないですが、githubでソースコードを公開し始めました。ニコニコ生放送でライブコーディング(という名のつぶやき放送)もやっております。
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.1
だって。
ここでふて寝しそうになったが、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でお仕事もしているらしいし。
Scala + MongoDB + Herokuで2chクローンを作る(連載第1回)
人生で3回2chクローン掲示板システムをプログラムし、運用したことがある。
まずはじめは、C++。boostを使ってテンプレート満載な構成だった。VC6でコンパイルできないパターンがあって泣いたっけ。コンパイルの「遅さ」にほくそ笑んでた。あの頃は若かった。
そのコードを使って東京工業大学掲示板というWeb掲示板システムを運用していた。C++では機動的な新機能開発が難しいことを、すぐに思い知った。当時使ったことがなかったPHPで試しにリライトしてみた。数時間で開発できちゃった。すぐリプレイスしちゃうよね。
ニコニコ大百科というWikiシステムを書いたときにも、付随する2ch式の掲示板システムを書いた。Rubyだった。Rubyで実用的なWebアプリケーションを書いたことがなかったが、これも難なく実装することができた。UTF-8を採用したので、トリップの互換性を取るのが面倒だった。
PythonではDjango/Kayそれぞれで掲示板が読み書きレベルでいるくらいまで書いたが、サービスインすることはなかった。
先日、@bibrostさんとお会いする機会があり、ScalaとMongoDBのよさを熱弁されていた。さらに、GraphDBの勉強会でも再度お会いし、これは運命(!?)というしかない状態。さらに、Python Dev. festaで、@voluntasさんがHerokuの話をしていて、そいえば使ったことないなー、と思った。
ピコーン!これらを組み合わせて、2chクローン作ってみて勉強すればよくね?俺の技術catch upは2chクローンを書くことから始まる、的な。
というわけで実録「新しいミドルウェア使うときは大体こんな感じでやるよね」的なエントリを連載します。
Scala始めるならまずこれ、コップ本を読みだした。最近技術書を通しで読めなくなっている僕にとってはキツい厚さ。とりあえず14章まで目を通す。また後で続き読む。
本を読み過ぎたので、浮気してどんなフレームワークを使うかをWeb検索をして考える。ScalaのWebフレームワークは、LiftとPlay! frameworkが著名か。UnfilteredとかScalatraもあるか。
ここで一考。サーバはデータだけを返して、各種レンダリングはクライアントでやらせよう。ほら今時はスマホアプリの時代ですよ。サーバはデータだけ返して、クライアントでレンダリングですよ。SEO的には悪そうだけど、まあスケールはしそうだよね。そういう観点だと、Webフレームワークは使う必要はないか。そしたら、Twitterのfinagleでも使ってみるかね。HTMLが欲しくなったら、薄いラッパをかませばいいし。@bibrostさんもそんな構成勧めてた気がする!
ScalaからMongoDBに接続するにはどうすればいいかなー。「Scala MongoDB」でGoogle検索。どれどれどんなドライバ使ってんのかなー…。1位、Salat。2位、lift-mongodb。3位、Casbah。ぎゃー。全部名前違うやんけ!不吉な香りがするぞ。
んでも、それぞれ中身を見てみると、SalatはCasbahを用いたO/Dマッパー、lift-mongodbはLift内にあるドライバらしい。Liftは使わないし、依存関係も軽くしたいので、Salat/Casbahを使うしかない。MongoDBを使うので、せっかくだったらO/Dマッパー使ってみるか。O/Rマッパーはあんまり使ったことないけど。O/Dマッパーって単語は正しいのかしら。
Rogueとか、Java阪ドライバのScalaラッパとかもあるらしい、という文章が目に入った気がするが、全力でスルー。
調査の結果、要件ざっくり決める。こんな感じだろ。
- Herokuでホスティングする
- Scalaで書く(はじめて)
- MongoDBをストレージとする(はじめて)、Salatでつなぐ
- finagle使う(はじめて)
- test firstで書いてみる(ニガテ)
- Webクライアントは、HTML5 + CSS3 + jQuery(まぁまぁ書ける)
「finagle MongoDB」でGoogle検索。
Heroku + finagle + MongoDBのサンプルコード発見!!
あ、でもコイツはRogueを使ってるな…まあいい。
「finagle Salat」でGoogle検索。
ムムム…意味ある検索結果が見つからない。
「scala MongoDB Heroku」でGoogle検索。
Scaling Out with Scala and Akka on Herokuってのが見つかる。
メモっとこ。
検索結果を見ていると、またよさげなものが見つかる。
A Quick WebApp with Scala, MongoDB, Scalatra and Casbah
Scalatraだけど、それ以外の部分は参考になりそう。
まあ、こいつらを見ながら悪魔合成的にテストコードを書いていけば、
Scala + MongoDB + finagle + Salat on Heroku
でコード書いていけるだろう。
タイミングがよいことに、今週は以下の2つの勉強会に参加することに。
MongoDB ソースコードリーディング
Scala勉強会第56回 in 渋谷
いろいろ情報仕入れられそう。
プログラムは、ニコ生のライブコーディングでガシガシ仕上げていく予定。
次回に続く。
MySQL Casual Talks #2で発表しました
MySQL Casual Talks #2にて発表してきました。「超カジュアルに使うMySQL」という内容で、いつものごとくジョーク枠担当です。
発表資料は以下のとおりです。
DeNAに転職いたしました
2011年6月、有限会社未来検索ブラジルを退職し、株式会社ディー・エヌ・エーに転職いたしました。
前職でお世話になった多くの方々にじゅうぶんに挨拶できていない状態で、大変申し訳ございません。現職では、スマートフォン向けソーシャルゲームの開発を行っております。今後ともよろしくお願いいたします。
渋川さんと一緒に撮影した写真を掲載します。
目が笑っていない人が僕です。
目をつぶっている人が僕です。
結論:写真映りもっとよくなりたいよね
SALSA AIR/Cosmolite/Liquis/ZERO Air、どのスーツケースを買うかで悩む
スーツケース選びで悩んでいる。
RIMOWA SALSA AIR 78cmと、Samsonite Cosmolite Spinner 74cmと、antler Liquis 75cmと、ZERO HALLIBURTON ZERO air。サルサエアーが一番容量がでかいが強度的に弱いのかも、コスモライトは貝殻の形状分容量が削られている、antlerは158cmの制限にひっかかる、ZERO airも角が削られている。
Samsonite Cosmolite Spinnet 74cmは実際使っているところを見たことがあり、すごくよいものだった。コストパフォーマンスもよいし。
寝かせて考える。
■RIMOWA SALSA AIR リモワ サルサエアー 92L/3.7kg 容量92L。重量3.7kg。滞在目安7泊〜10泊の新基準大型サイズ。小回りの利く機...
販売価格: 56,700 円 (2012/5/21 13:15 更新)
販売店舗: カバンのセレクション
●カラー:ブラック、シルバー、レッド、ブルー、バイオレット●素材:カーヴ製※カーヴ素材:ポリプロピレンを層に重ねた構造で、耐久に優れた軽量な素材●サイズ:74×...
販売価格: 34,650 円 (2012/5/21 13:15 更新)
販売店舗: GULLIVER Online Shopping
10P18May12【送料無料】Antler Liquisアントラーリクイス75cm スーツケース
素材 :特殊ポリカーボネート(鏡面パール仕上げ) 本体サイズ(本体) :約75×54×34cm本体重量 :約3.8kg容量 :約104L 付属品 :特製ラゲッジ...
販売価格: 29,400 円 (2012/5/21 13:15 更新)
販売店舗: GET
JavaでPlay frameworkを試しに遊んでみるもさっそく詰まったでござる
ひさびさにJavaを触りたいと思っていた。理由はすっかり忘れているから。
ニコ生のプログラミング放送で、どのフレームワークでWebシステムを作ったほうがいいか聞いてみたところ、Play frameworkというのをおすすめされた。Java製(ツール類はPython製もあり)。というわけで、チュートリアルを触って遊んでいたら、いきなり詰まった。初のテストを書く部分で。
以下のようなエラーが自動コンパイル時に発生する。
Compilation error (In /app/models/user.java around line 9) The file /app/models/user.java could not be compiled. Error raised is : The public type User must be defined in its own file
user.javaというファイル名なのにUserというpublicクラスを定義しようとしてエラー。しかし、ファイルシステム上のファイル名はきちんとUser.javaなんだが…
試しに、アプリケーションのtmpディレクトリを削除いてみたら、エラーが変わり
Compilation error (In /app/models/user.java around line 9) The file /app/models/user.java could not be compiled. Error raised is : The type User is already defined
と出るようになった。
原因は、初のテストを書く部分で
User bob = user.find("byEmail", "bob@gmail.com").first();
と書いていたこと。
User bob = User.find("byEmail", "bob@gmail.com").first();
に修正したら動いた。
名前空間にuserがない -> user.javaというファイルをmodel内で探す -> open時にファイル名の大文字小文字を区別しないシステムだと(Windows/Mac) User.javaをuser.javaでも開ける -> Userの多重定義もしくはファイル名との相違
ということらしい。うおお。
Twitterで、ひさびさに触ったJavaにまだHashMapのリテラルがないことを嘆いてみた。しかし、HashMapのリテラルがないことが、Javaの設定ファイル文化を推し進めているのかな?AnnotationsがついたあたりからJavaを触っていないのでしばらくリハビリしてみる。



