今日はScalaで動作するマイクロHTTPフレームワークであるCaskを紹介したいと思います。Scalaを見る目が変わると思います。Scalaはけっこう複雑寄りのエンタープライズグレードの超強いライブラリが多いような気がするのですが、今回ご紹介するのはとてもeasyで便利なやつです。僕は当分これでいいと思います。
この記事は、はてなエンジニア アドベントカレンダー12日目です。
昨日の記事はmob programming を續けてみた - c4se記:さっちゃんですよ☆でした。モブプロ自分のチームでもたまにやってるのですが、目玉が多いだけあってどんどん進みますよね。すごく疲れるけど・・・
Caskって何よ
CaskはPythonのFlaskなどにインスパイアされたScalaのマイクロHTTPフレームワークです。
作者はScalaのOSS界では高品質なプロダクトをどんどん作っていることでおなじみのlihaoyiさんです。
元来、Scalaにはhttp4sやPlay Framework、Akka HTTPなどのHTTPフレームワーク/ライブラリが多く用意されています。そんな中なぜフレームワークを作ったのでしょう?
作者のlihaoyiさんによれば、CaskはPythonのFlaskにインスパイアされた、シンプルさ・柔軟さ・使いやすさを主眼に置いたフレームワークだそうです。
従来のライブラリはシンプルさと多機能さを重視するあまり、初心者にとっては暗号のようなDSLを駆使しなければならなかったりと、Node.jsやPythonなどの文化と比べるとかなりハードルが高くなりがちでした。
Caskでは他の多くのライブラリで採用されているバックプレッシャやストリーミングといった専門的な機能を捨ててシンプルなHTTPサーバとして割り切ることで、素朴なScalaで書けるとても使いやすいAPIを実現しています。
Scala Scriptで書いてみよう
試しにCaskでちょっとしたサーバを書いてみましょう。CaskはsbtやmillといったScalaのビルドツールで利用できますが、Scala CLIを使うのが一番簡単です。適当なディレクトリにserver.scala
を作成し、以下のように記載しましょう。
//> using scala "3.3.1" //> using dep "com.lihaoyi::cask:0.9.1" object MyApp extends cask.MainRoutes: initialize()
Scala CLIは以下の手順を参考に簡単にインストールできます。
コードを見ての通り、cask.MainRoutes
を継承したオブジェクトを作ってinitialize()
を呼ぶだけで最小限のサーバが起動します。
実際にscala-cli
コマンドで起動してみましょう。
% scala-cli server.scala
これだけでlocalhost:8080
にHTTPサーバが起動します。何もエンドポイントが無いので、どこにアクセスしても404を返します。
エンドポイントを生やしてみよう
これだけだと404しか返さないのでエンドポイントを書きます。メソッドにアノテーションを付けるとそれがそのままエンドポイントになります。めちゃ簡単じゃないですか?
//> using scala "3.3.1" //> using dep "com.lihaoyi::cask:0.9.1" object MyApp extends cask.MainRoutes: @cask.get("/") def hello(who: String = "Cask") = s"Hello, $who !" initialize() end MyApp
これでサーバを起動すると、who
をクエリパラメータに受け取るエンドポイントが動作します。
% curl localhost:8080/?who=Windymelt
Hello, Windymelt !
もちろんPOSTも対応できるし、JSONやフォームデータの受け取りにも対応してます。JSONライブラリとしてデフォルトではujsonが勝手に付いてきますが、ユーザが少し書けばCirceなどの本格的なJSONライブラリも利用可能になっているようです。
//> using scala "3.3.1" //> using dep "com.lihaoyi::cask:0.9.1" object MyApp extends cask.MainRoutes: @cask.get("/") def hello(who: String = "Cask") = s"Hello, $who !" @cask.get("/json") def helloJson() = ujson.Obj( "hello" -> "Cask JSON!" ) @cask.post("/do-thing") def doThing(request: cask.Request) = request.text().reverse @cask.postJson("/do-thing-json") def doThingJson(key1: String, key2: Option[Int]) = ujson.Obj( "length_of_key1" -> key1.length, "double_of_key2" -> 2 * key2.getOrElse(42) ) initialize() end MyApp
何の変哲もないScalaのコードなので、初心者にもかなり取っつきやすく、上級者でもだいたい十分な機能を備えていると感じます。
どうして紹介したかというと最近Honoを触って体験が良かったから
Caskを紹介しようと思った背景には、最近触ったHonoの存在があります。Honoもまた、ミニマルなHTTPフレームワークであり、エッジで動作することを前提に開発されたNode.jsライブラリです。
最近仕事でHonoを使うことがあって、その書き味の良さに感激したんですよね(というか、はてなエンジニアはこういうミニマルで透明なものを好む傾向があると思います)。
何が琴線に触れたのかな〜と思い返してみたのですが、「ベースラインが超簡単であること」「やりたい事の難しさに応じて徐々に難しくなるような比例関係が成立していること」「言語が元々持っているセマンティクスを尊重すること」が良さだと感じました。
これらの特性を満たしているツールは楽しくサクサク書き進めることができるように思います。こういったツールが増えないかな〜と思っていたところCaskを知り、これもまた使い勝手がとても良かったので紹介しようと思い立ったというわけです。
Funな書き味を目指して
さて、僕はScalaの日本語コミュニティ『Scalaわいわいランド』を共同運営している程度にはScalaが好きで、普及のために色々なことを考えているのですが、最近の課題として、初学者がスムーズにScalaを使えるようになってほしいけれど教材として使えるライブラリは一級品である代わりに難しくなりがち、という意識がありました。業務の目線でも、ちょっとした仕事をしてほしいだけなのに、オーバーキルなライブラリを使わなければならない、それゆえにメンテナンスコストが上がって採用しづらいという矛盾を感じていました。
Caskのようなeasyなツールは専門的な機能を切り離していますが、だからといって力不足だとか使えないということは決してなく、十分強力でありながら専門知識をあまり要求せず、エンジニアのfunを満たしてくれる、そんなツールだと思います。
こうしたeasyでfunなツールが増えていけば、Scalaを使ってみる間口が広がっていき、分厚いユーザ層を構築できるのではないかと考えています。やっていくぞ!
明日は
明日は @_maiyama18
だッッッ!!!