こんにちは。adingo で広告配信のお手伝いをしている井手上です。
今回のエンジニアブログは Haskell の Socket.IO サーバーライブラリを使って、Yo みたいなアプリケーションを作成した話です。
前置き
突然ですが、皆さんはお仕事や個人的な開発ではどんなプログラミング言語・ミドルウェア・プラットフォームを利用していますか?
Web アプリケーションを作るなら Python や Ruby 等のいわゆる Lightweight Language、大量のリクエストを高速に、並列に処理したいのなら Erlang など、目的に応じた選択肢があると思います。色々な選択肢がある中、以前は最適だと思って選択したものが、年々新しい技術がが出てきているために今ならもっと良い選択ができるという状況になることもあるかと思います。
私が働いているチームでは採用ページの「業務内容」に書いてあるとおり「合理的な理由がありチームにとって受け入れ可能であれば別な技術の採用や別な技術へのリプレースを拒む理由はありません」ということで、新しい技術での開発やリプレースがよく行われています。最近ですと、Perl で書かれていた広告配信システムの一部機能を自由に高速化するコンペを行い、優勝したシステムを採用するという取り組みを行い Golang 製のシステムが優勝しました。自分も最近始めた Haskell をどこかに投入してやろうと虎視眈々と狙っているところです。
その Haskell ですが、実際に投入するかどうかはともかく、関数型プログラミングや型システムといった多くのプログラミングパラダイムを学べるお得な言語です。自分が知らないパラダイムを学ぶことは問題の捉え方や問題解決のための選択肢を増やすという点で役立ちます。今回は、Socket.IO 1.0 サーバーの Haskell 実装が出てきたということで使ってみました(前置きが長い)。
下記の例は addPerson というメッセージがきたときに、data に name と age というプロパティが存在するかチェックしている例です(JavaScript版)。age は数値を期待されているはずなので、数値であるかどうかのチェックまで必要かなと思ったり心配事は多いです。
次は同じ例の Haskell 版です。addPerson メッセージがきたときに JSON データを Person データ型に変換する処理が入ります。その時に name や age が存在するか?フィールドの型として正しいか?というようなことがチェックされます。データ型を定義しておけばその定義に沿わない JSON はパースできないので、パースとデータのチェックが同時にできます。
上記の例を比較してみると、JavaScript 版の方がコード量は少なくカジュアルに書けそうですね。アプリケーションが行う通信でのインターフェースをしっかり決めて、それが複雑であったり、データのチェック項目が多い等の場合は Haskell でサーバーを書くのもありかもしれません。
KaitenZushi
Socket.IO 1.0
Socket.IO はクライアントとサーバーとのリアルタイム通信を実現するフレームワークです。チャットアプリケーションやドキュメントの共同編集機能のように、リアルタイムなやりとりに利用されます。クライアントには主にブラウザが挙げられ、通信プロトコルには WebSocket を利用しています。Socket.IO は 2010 年からあるようですが、2014 年 5 月に Socket.IO 1.0 となりました(GitHub リポジトリのコミット履歴より)。
個人的に Socket.IO 1.0 で面白いと感じている点は、大きく分けてモジュールが Socket.IO と Engine.IO に分かれている点です。Engine.IO はトランスポート層のプロトコルの違いを吸収することに責任を持ちます。Socket.IO には Engine.IO よりもアプリケーション寄りの高レベルな機能が実装されており、自動再接続や名前空間機能を提供しています。
Engine.IO でのトランスポート層プロトコルの違いを吸収する方法について簡単に説明しますと、接続開始時は接続できる可能性が高いロングポーリング方式で通信を開始し、WebSocket での通信が可能ならば、WebSocket に切り替えるというように、upgrade していく方式でトランスポートの違いを吸収しています。WebSocket 接続のタイムアウトを待ってロングポーリングに切り替えるというような fallback 方式ですと、最初はつながらないので、WebSocket を使用できないユーザーにとっては接続までが長くてイライラしてしまうかもしれません。upgrade 方式だとはじめから接続できるので、ユーザー体験は fallback 方式よりも悪くないことが期待できます。
ocharles/engine.io
Socket.IO というと、公式の提供しているサーバーが Node.js 製なので、サーバーも JavaScript で書くイメージがありますが、プロトコルさえ実装されていれば良いので、サーバーは Node.js でなくても構いません。今回使用する ocharles/engine.IO は Socket.IO 1.0 を Haskell で実装したサーバーになります。
Node.js よりも Haskell でサーバーを書くことの良い点は、期待しているデータの型を定義しておけば JSON のパース時にデータのチェックを行う必要がないというところかなと思います。
下記の例は addPerson というメッセージがきたときに、data に name と age というプロパティが存在するかチェックしている例です(JavaScript版)。age は数値を期待されているはずなので、数値であるかどうかのチェックまで必要かなと思ったり心配事は多いです。
次は同じ例の Haskell 版です。addPerson メッセージがきたときに JSON データを Person データ型に変換する処理が入ります。その時に name や age が存在するか?フィールドの型として正しいか?というようなことがチェックされます。データ型を定義しておけばその定義に沿わない JSON はパースできないので、パースとデータのチェックが同時にできます。
上記の例を比較してみると、JavaScript 版の方がコード量は少なくカジュアルに書けそうですね。アプリケーションが行う通信でのインターフェースをしっかり決めて、それが複雑であったり、データのチェック項目が多い等の場合は Haskell でサーバーを書くのもありかもしれません。
KaitenZushi
今回は Socket.IO を使ったアプリとして、KaitenZushi という Web アプリケーションを作ってみました。これは最近話題になった Yo というアプリを意識しています。Yo は誰かに「Yo」というメッセージを送るだけのシンプルなアプリです。例えば朝方に Yo と言われると、(たぶん)おはようの意味だったりと、人同士の関係や時間等々の様々なコンテキストを考えて、Yo を受け取った側が何となく解釈するという大変ハイコンテキストなアプリです(自分の理解では、ですが)。
KaitenZushi はもう少しコンテキストを絞ったらどうなるのかな?という考えで作りました。右上の入力ボックスからコンテキスト(コンテキスト = 寿司ネタをイメージしてます)を作成して、寿司(コンテキスト)を、同じコンテキストにいる人たちにブロードキャストできます。なかなか用途がわからないアプリだと思いますが、自由に寿司を配ってみてください。
KaitenZushi のリポジトリは以下になります。サーバー側のコードはとても短く、寿司がサーバーに emit されたら、接続している人全員に寿司を broadcast するという機能だけです。
ちなみに今回は Node.js で Haskell 版と同じ機能を実装して heroku 上にデプロイしています。ocharles/engine.io は Snap や Yesod といった Haskell 製の Web アプリケーションフレームワークにも対応しており、heroku もそれらに対応しているのですが、今回は時間の都合上、サクッとデプロイできた方を使用しました。
まとめ
Haskell の勉強ついでに KaitenZushi という Socket.IO 1.0 を利用した Web アプリケーションを作ってみました。まだまだ道半ばですが、自分の知らないプログラミングのパラダイムを学ぶことは楽しいです。
最後に、弊社には ajito という社内バーがあり、エンジニアが夜な夜な出現してビールを飲んだり技術的な話をしたりと楽しい場になっています(#ajiting といいます)。弊社エンジニアに一声掛けてくださると、無料でビールを飲みつつ歓談できるかと思います。また、勉強会の場としても提供していたりするので、どんどんお声掛けください!そして、KaitenZushi にも ajiting のコンテキストを作ったので、いらっしゃった時には使ってみてください!
参考