はじめに

sprayはRESTやHTTPを扱うためのScalaの軽量なライブラリです。また、akkaという別のライブラリを使用しておりアクターモデルを用いて非同期でブロッキングしないアプリケーションを作成可能です。

今回はその中からspray-canspray-routingspray-jsonjson4sあたりを使って簡単なREST APIを構築してみましょう。

プロジェクトの作成

build.sbtに追記して必要なライブラリ等を取り込んでいきます。

例によってsbt-revolverを入れたいのでプロジェクトのほうにも以下を追加しておきます。

ルーティングができるリスナーを作る

リクエストを捌くためのリスナーを、spray-routingを利用して作ります。spray-routingを使うことで直感的に記述することができます。

このようにreceiveにrunRouteを渡してやり、その中にゴリゴリとルーティングを記述します。
今回は使っていませんが、変数に代入したりもできるためルーティングが複雑化してきてもすっきりさを保つ事ができます。

ルーティングの書き方

前半のpath("ping")のブロックはspray-canの時の/pingを叩いたらPONGが返ってくる部分です。
その後ろ、~で別の条件をどんどん足していけます。

pathPrefix("user")はpathがuserで始まっているものが該当します。そしてブロック内にさらに条件を追加することでより下位のパスについて絞り込んでいくことができます。

この例だと、pathEndだと/userが、path("")だと/user/がそれぞれ対応します。そして|はorになるので(pathEnd | path(""))と書くことで/user/user/にまとめて同じ挙動を設定しています。(ちなみにこの挙動、pathEndOrSingleSlashと書けば一発で実現できます。後から知りました。)

path(Segment)とすることで、パスの文字列をそのまま次の処理で使うことができます。username =>と書いてある部分がそれで、以後ブロック内でusernameという名前で使えます。(もちろん名前は好きに変えられます)

レスポンスの返し方

ルーティングはこのMyListenerで行ってしまいますが、レスポンスは別のactorに返させたいと思います。

レスポンスを返すにはcompleteを実行してやれば良いのですが、これはcomplete("hogehoge")という書き方の他に、RequestContextを受け取ってctx => ctx.complete("hogehoge")というように書くこともできます。RequestContextのcompleteが実行できれば良いので、このctx(RequestContext)さえ渡してしまえば他のActorからでもレスポンスを返すことができます。

参考:spray-routing / Key Concepts / Routes / Constructing Routes

今回の例でいえば、ルーティングを行うMyListenerを作るときに合わせて別のActorのActorRefを受け取るようにして、こいつに対してRequestContextとメッセージを渡して処理を丸投げしています

レスポンスを返すServiceActor

ではMyListenerからRequestContextとメッセージを受け取ってレスポンスを返すServiceActorを作りましょう。

だいぶすっきりしていますが、要はRequestContextとメッセージを受け取ったらメッセージをユーザー名とみなしてユーザーデータを作成し、それをレスポンスしているだけです。

このとき、レスポンスがJSONに変換可能であれば変換するようにJson4sSupportをミックスインしています。
(これをやらないとちゃんとしたレスポンスとみなされなくって「UserDataって何!」と怒られます。)

JSONでレスポンスするために

注目したいのは下半分のMyServiceです。
ここではレスポンスのためのclassと、それをJSONへと変換するためのJsonProtocolを定義しています。

scalaのcase classはとても便利で、クラスを新しく作るとき合わせて作ることの多いものをまとめて自動で作ってくれます。UserDataは値を二つもつので、値を二つ持つUserDataをJSONに変換できる関数UserFormatを準備しておきます。これで、UserDataをJSONに変換しようとした場合にこのUserFormatが呼び出されあっさりとJSONが生成されます。

(もしなにか独自の形式でJSON化したいのであればjsonFormat2(UserData)の部分をどうにかすることになるでしょう)

このあたりは”型クラス”とかそういう概念らしいです。(正直な話まだちょっとよくわかっていませんが・・・)

参考:Scala の implicit parameter は型クラスの一種とはどういうことなのか

Main.scala

あとはspray-canの時同様Http.Bindさせる部分を作ります。

今回はMyListenerだけではなくServiceActorも準備しています。そしてこれをMyListenerに渡すことで、MyListenerからServiceが呼べるようになっています。

これを実行して http://localhost:8080/User を叩けばテストユーザーの情報が、 http://localhost:8080/User/hogehoge とすればhogehogeさんのユーザー情報が取得できます。

最後に

いかがだったでしょうか?前回のspray-canだけの時はただなんとなく動いてる、って感じでしたがこうやってルーティングが設定されると途端にそれっぽく感じますね。REST、といいつつGETしかありませんが、こんな調子でPOSTやPUTにも対応させることが可能です。

上から順番に正規表現を実行してマッチしたControllerが叩かれる・・・なんてルーティングだと大きくなればなるほど意図しない動きをして頭を悩ますことがありますが、この形式であれば動作もわかりやすく、人間が見ても簡単に動きを把握することができるでしょう。さらには、”~で条件を足していく”という性質上ルーティングのファイル(クラス)を分けることも簡単にできます。僕はこの点に一発で惚れこみました。

使えば使うほど魅力に気が付けるsprayをぜひ皆様も使ってみてください。