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
- 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のフォーマット内にリッチなエラー情報を埋め込んで欲しい
以下、各言語で未完成ながらもミドルウェア選定・実装をしてみた感想を述べる。
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によるロギング
- 死活監視、リソース監視
現時点の僕の実装は、各言語とも完成度が低い。同じハードウェアで計測する場合でも、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は合致してそうなんだよなー…