PHPerによるScala入門その2 APIサーバー
概要
何をやってるかは前回書いたのでいいとして、APIサーバーをScalaで立ててみた記録。
ライブラリ
JVM上で動作するWebサーバーを作った経験が無いのでScalaでWebサーバーを実現する方法をもろもろぐぐってみた。Scalaだと Akka HTTP
, Play
Scalatra
辺りがよく使われている?
調べて出てきたけど名前忘れたのも何かあったけど忘れた。
- Akka HTTPは、Akkaの知見がまったく無いので今回は見送り
- Play はちょっとAPI動かしたいだけなのでToo muchが過ぎる
- Scalatra は最新版が2017年11月のようなのでちょっと・・・
ということで、これがいいんじゃないかっていうものが無かったので困った。
なので社内のScalaエンジニアに相談してみた。 結果として、Akka HTTPはそこまでアッカアッカしてないからいいんじゃないか、とのこと。Akkaは後々触ろうと思ってたし、Actorの前提知識が無くて済むならいいか、ということでAkka HTTPを使うことにした。
API
Akka HTTP
Akka HTTPのイントロダクション をやったらどう書くのかはある程度分かってきたのでこれを参考にAPIを作ってみる。とは言うもののどうしてこう書けるのかが全然すっと読めない。
例えば
val route = get { pathPrefix("item" / LongNumber) { id => val maybeItem: Future[Option[Item]] = fetchItem(id) onSuccess(maybeItem) { case Some(item) => complete(item) case None => complete(StatusCodes.NotFound) } } }
こういうrouteの定義について、どこがメソッドでどこが引数なのか、とか分からん。慣れの問題なのかなぁ。
sprayのjson formatも
implicit val itemFormat: RootJsonFormat[Item] = jsonFormat2(Item) implicit val orderFormat: RootJsonFormat[Order] = jsonFormat1(Order)
こう書いておくと ToResponseMarshallable
として扱えるようだけどこのimplicit valがどこで必要とされてるか、どうやって追ったらいいの・・・?
State
Scalaが読めないのはそれはそれとして、PHPとの違いで驚いたのがWebサーバーが状態を持つこと。 若干語弊がある気もするけど、例えば↓のようなコードを書いたとして
object Server { implicit val system = ActorSystem() implicit val materializer = ActorMaterializer() implicit val executionContext = system.dispatcher private var value: String = "default" def apply(): Unit = { val route = get { path("string") { complete(s"value: $value") } } ~ put { path("string") { entity(as[String]) { v => value = v complete("ok") } } } // ... }
PUTで書き換えたクラス内の値が次のGETリクエストで引ける。
おそらくこれはJavaやScalaを書いてる人には当たり前のことなのだろうけど、普段PHPばっかり書いてる自分にはかなり驚き。まぁGoとか他の言語でもそうなんだろうが・・・ PHPだとリクエストごとにファイルが読まれて(シングルトンであろうと)クラスのインスタンスが生成されて常にまっさらな状態から始まるので、こういう挙動はしない。この前提でコードを書いてると、リソースやハンドラは明けたら閉じる、作ったら消すというのが当たり前になるのは分かるな。PHPは変にロックを取らなければプロセスが死ねば大体開放されるので気にしなくても動くっちゃ動く。良くないけど。そういうのを考えるとPHPもよく出来てるなぁと感じる。
Logger
まぁAPIサーバーではないけども、 GitHub - lightbend/scala-logging: Convenient and performant logging library for Scala wrapping SLF4J. を参考にしてloggingできるようにしておく。
API
このAPIサーバーはKafkaへのProviderなので、とりあえずKafkaへのProviderのガワだけ作っておいて後で実装することにする。
trait QueueRegister { def register(queue: Queue) } object KafkaProvider extends Register { val logger = Logger("queue") def register(queue: Queue): Unit = { logger.info("logging sample") } }
とりあえずこんな感じで書いてみたものの、Kafkaを使うという知識が外部に露出してしまうからこの辺はなんとかした方が良さそうな感じがする。
Queue
自体はドメイン知識を持ったモデルとして使いたいのでその情報だけ持つとして、どのQueue, Messagingツールを使うかはconfig的なものを使って隠蔽する方が良さそう。こういうのScalaだとresourcesに書くのか。使い方分からないから調べなければ。
アプリケーション側からはQueueRegisterのregisterを呼ぶだけで勝手に解決して登録してくれるようにしてみよう。
traitが明確に型として扱えるから、ちゃんとAOPができるんだろうな。昔の上司に、「PHPのTraitは壊れてる。ScalaのTraitが正しい」って言われたのはこの辺なんだろうか。
KafkaにQueueを登録するって書いてるけど、KafkaのコンテキストだとQueueって言わないのでは・・・?