PHPerによるScala入門その3 sbt

昔はsimple build toolの略だったそうですね。全然simpleじゃねーだろということで変わったとか。

sbt

入門者向けの記事とか見てるとbuild.sbtってすごく簡素なんだけど、勉強用のリポジトリのやつも今のところこんなんだし。

name := "queue"
scalaVersion := "2.12.6"
version := "1.0"
scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked", "-Xlint")

libraryDependencies ++= Seq(
  "org.scalatest" %% "scalatest" % "3.0.5" % "test",
  "org.mockito" % "mockito-core" % "2.13.0" % "test",
  "com.typesafe.akka" %% "akka-http"   % "10.1.8",
  "com.typesafe.akka" %% "akka-stream" % "2.5.19",
  "com.typesafe.akka" %% "akka-http-spray-json" % "10.1.8",
  "com.typesafe.scala-logging" %% "scala-logging" % "3.9.2",
  "ch.qos.logback" % "logback-classic" % "1.2.3",
)

scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked", "-Xlint")

ここについては社内のリポジトリからパクったやつなのでちゃんと意味は分かってないがなんとなく想像つくので入れてみている。

それで、入門記事とか見てこういう風に書くというのを知った後でちゃんとしたプロジェクトのbuild.sbtを見に行くと全然読めない。

lazy val foo = (project in file("."))
  .settings(
    name := "name",
    scalaVersion := "2.12.7",
  )

のような感じになっていて理解ができない。改めて見てみるとなんとなく分からなくはないが入門向けのものと本番のものとで違いがでか過ぎて困る。

とか思ってたら別の調べ物をしてたときにsbtのサイトを見つけた。あったのか・・・

sbt Reference Manual — sbt Reference Manual

この中のビルド定義のページを読んだら何がどうなっているかおおむね理解できた。

:= の意味も分からなかったのだがこれはメソッドだったんだな。

変更後

当初のbuild.sbtがイケてなかったので書き直した

scalaVersion := "2.12.6"

version := "1.0"

lazy val commonSettings = Seq(
  scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked", "-Xlint"),
  libraryDependencies ++= Seq(
    "org.scalatest" %% "scalatest" % "3.0.5" % "test",
    "org.mockito" % "mockito-core" % "2.13.0" % "test",
    "com.typesafe.scala-logging" %% "scala-logging" % "3.9.2",
    "ch.qos.logback" % "logback-classic" % "1.2.3",
  ),
)

lazy val domain = (project in file("domain"))
  .settings(
    commonSettings,
    name := "queue_domain",
  )

lazy val provider = (project in file("provider"))
  .settings(
    commonSettings,
    name := "provider",
    libraryDependencies ++= Seq(
      "com.typesafe.akka" %% "akka-http" % "10.1.8",
      "com.typesafe.akka" %% "akka-stream" % "2.5.19",
      "com.typesafe.akka" %% "akka-http-spray-json" % "10.1.8",
    ),
  )
  .dependsOn(domain)

lazy val consumer = (project in file("consumer"))
  .settings(
    commonSettings,
    name := "consumer",
  )
  .dependsOn(domain)

こんな感じにした。 LogとかTestは共通なのでcommonSettingsに、WebサーバはProviderでしか使わないのでAkkaHTTPをProvider配下に、Queueクラスとかドメイン層は共通で使われるので別のプロジェクトにしてproviderとconsumerから呼び出すようにした。 なるほど、ようやく社内のリポジトリのbuild.sbtが読めそう。build.sbtを見れば大体そのプロジェクトがどういう構成になってるか分かるよ、と言われたのが少し理解できた気がする。あーだいぶスッキリした。

packageとディレクト

この変更をするにあたり、ディレクトリ構成も変更した。 もともとは

├── src
│   └── main
│       ├── resources
│       └── scala
│           └── com
│               └── shmrkm
│                   └── queue
│                       ├── client
│                       ├── consumer
│                       └── provider

こんな形にしていたのだけど、consumerとproviderは別のサブプロジェクトになるはずなので明確に分けるようにした。変更後は↓のようになった。

consumer
├── src
│   └── main
│       ├── resources
│       └── scala
│           └── com
│               └── shmrkm
│                   └── queue
│                       └── consumer

domain
├── src
│   └── main
│       ├── resources
│       └── scala
│           └── com
│               └── shmrkm
│                   └── queue
│                       └── domain

provider
├── src
│   ├── main
│   │   ├── resources
│   │   └── scala
│   │       └── com
│   │           └── shmrkm
│   │               └── queue
│   │                   └── provider

Provider用のMainを書いてるけどConsumer用のMainを書きたくなったらどうすんだろ、と思ってたけどこうすれば良かったんだな。

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リクエストで引ける。

おそらくこれはJavaScalaを書いてる人には当たり前のことなのだろうけど、普段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って言わないのでは・・・?

PHPerによるScala入門その1 環境セットアップ

前置き

phperがscalaを使って適当webサーバを立ち上げたときの学習の記録です。副業で使うための学習が主目的なので作るもの自体に意味はありません。 コードは↓に上げていく予定です。 https://github.com/shmurakamu/scala_queue

作るもの

CQRS的にQueueのproviderとconsumerを作ります。providerはAPIサーバとして動かし、kafkaにエンキューします。consumerはkafkaからsubscribeしてdynamodbに適当に値を保存します。

準備

Scala

まずScalaのコードを書く準備をします。 以下の前提です。 - sbtはインストール済み - 基本的なScalaの記法は学習済み

この辺はドワンゴさんのScala研修テキストとか入門書とか、その辺を見れば良さそう。

プロジェクト作成

ということでIntelliJ IDEAでプロジェクト作成開始。 New > Project だったか Project from existing sourceだったかその辺りから作成して、予め設定していたsbtとかJDKの設定をする。

正しいディレクトリ構成がよく分かっていないので、適当に見繕ったプロジェクトを参考にして

src
├── main
│   ├── resources
│   └── scala
│       └── com
│           └── shmrkm
│               └── queue
│                   ├── client
│                   ├── consumer
│                   └── provider
└── test
    └── scala
        └── com
            └── shmrkm
                └── queue
                    └── provider

とりあえずこんな感じで作ってみた。

build.sbtも何を書いたらいいかまだよく見えてきてないので

name := "queue"

scalaVersion := "2.12.6"

version := "1.0"

scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked", "-Xlint")

libraryDependencies ++= Seq(
  "org.scalatest" %% "scalatest" % "3.0.5" % "test",
  "org.mockito" % "mockito-core" % "2.13.0" % "test",
)

ひとまずこんな感じにした。

さてコード書くかーと思って src/main/scala/com/shmrkm/queue/provider 以下に .scalaファイルを作ってみたけどなぜかコードハイライティングがいまいち効いてない。 class とか object とかは色が変わるのだけど、変数とかメソッド名の色が変わらない。ライブラリのコードはちゃんと色が変わるのになぜ・・・?

よく分かってないけど、 src/main/scala を Mark Directory as で Load Path Root に設定したらちゃんとハイライトされた。これ必要なのかな?

とりあえずセットアップはそんな感じで。