play-json で snake_case な json を camelCase な case class にマッピングする

play-json を使えば json を case class にマッピングすることは簡単にできますが、json のキーがそのまま case class のフィールドに対応するため、snake_case な json API を case class にマッピングするためには、case class のフィールドも snake_case にする必要があります。Scala は camelCase にするのが主流ですから少し気持ち悪いですね。

snake_case な json を camelCase な case class にマッピングするためには、いつも使っている Json.format[T] のラッパーを作る必要があります。作りました。

tototoshi/play-json-naming · GitHub

build.sbt に

libraryDependencies += "com.github.tototoshi" %% "play-json-naming" % "0.1.0"

して使ってください。

使い方は、JsonNaming.snakecase を呼び出すだけの単機能ライブラリです。

import com.github.tototoshi.play.json.JsonNaming

case class Name(firstName: String, lastName: String)
case class User(id: Int, nameData: Name)

// Json.format[T] をラップする
implicit val nameFormat = JsonNaming.snakecase(Json.format[Name])
implicit val userFormat = JsonNaming.snakecase(Json.format[User])

val jsonString = """{"id":1,"name_data":{"first_name":"Toshiyuki","last_name":"Takahashi"}}"""

Json.parse(jsonString).validate[User]
Json.toJson(User(1, Name("Toshiyuki", "Takahashi")))

イメージとしては

          +---------------+
          |  json string  |
          +---------------+
                 ⇅  
          +---------------+
          |    JsValue    |
          +---------------+
                 ⇅  JsonNaming.snakecase
          +---------------+
          |    JsValue    |
          +---------------+
                 ⇅  
          +---------------+
          |  case class   |
          +---------------+

こんなかんじで、json の内部表現である JsValue に対して変換をかけています。

snake_case 以外にもよくある規則があれば取り込みますので pull-req ください。

Play 2.4 の Module の作り方と Plugin からの移行について

Play 2.4 では今までの Plugin の仕組みが deprecated となり、 新たに Module という仕組みが導入されています。

Module はこれまた新たに導入された Runtime Dependency Injection の上に乗っかっています。 Play では Guice をデフォルトの DI 実装として利用しますが、 Guice 以外の実装を利用するユーザーもいるだろうということで、 ライブラリとして提供するモジュールについてはそこだけ Guice ではなく、Play 独自の Module という仕組みに沿った形で実装します。

アプリケーションの起動時と終了時にメッセージを表示する Play Module を実装してみましょう。

まずは処理の本体です。

package example

import play.api._
import javax.inject._
import play.api.inject._
import scala.concurrent._
import scala.concurrent.ExecutionContext.Implicits.global


@Singleton
class Hello @Inject() (lifecycle: ApplicationLifecycle) {
  lifecycle.addStopHook(() => Future.successful { println("Goodbye") })
  println("Hello")
}

今までの Plugin インタフェースのメソッドを実装する形で実装していた、 Play アプリケーションの起動時の処理は、シングルトンクラスの初期化処理の中で行います。 終了時の処理は injection された lifecycle オブジェクトの addStopHook メソッドを利用して登録します。

これを Play Module として読み込むには play.api.inject.Module を継承して、binding メソッドを実装します。

package example

import play.api._
import javax.inject._
import play.api.inject._
import scala.concurrent._
import scala.concurrent.ExecutionContext.Implicits.global

class HelloModule extends Module {
  def bindings(environment: Environment, configuration: Configuration) = {
    Seq(
      bind[Hello].toSelf.eagerly
    )
  }
}

bind の仕方を DSL で記述します。 Play アプリケーション起動時の処理を行うためは、起動時に bind する必要があるので、 .eagerly を使用します。

作った Module を利用するときは application.conf でその Module を有効にします。

play.modules.enabled += "example.HelloModule"

シングルトンオブジェクトなので toSelf で自分自身に bind してしまいましたが、 より一般的にはインタフェースを作ってそれに bind します。

package example

import play.api._
import javax.inject._
import play.api.inject._
import scala.concurrent._
import scala.concurrent.ExecutionContext.Implicits.global

class HelloModule extends Module {
  def bindings(environment: Environment, configuration: Configuration) = {
    Seq(
      bind[HelloInterface].to[Hello].eagerly
    )
  }
}

@Singleton
class Hello @Inject() (lifecycle: ApplicationLifecycle) extends HelloInterface {
  lifecycle.addStopHook(() => Future.successful { println("Goodbye") })
  println("Hello")
}

trait HelloInterface

さらに、インスタンスの生成が複雑な場合は Provider を利用することもありそうです。

package example

import play.api._
import javax.inject._
import play.api.inject._
import scala.concurrent._
import scala.concurrent.ExecutionContext.Implicits.global

class HelloModule extends Module {
  def bindings(environment: Environment, configuration: Configuration) = {
    Seq(
      bind[HelloInterface].toProvider[HelloProvider].eagerly
    )
  }
}


@Singleton
class HelloProvider @Inject() (lifecycle: ApplicationLifecycle) extends Provider[HelloInterface] {
  lazy val get = new Hello(lifecycle)
}

class Hello (lifecycle: ApplicationLifecycle) extends HelloInterface {
  lifecycle.addStopHook(() => Future.successful { println("Goodbye") })
  println("Hello")
}

trait HelloInterface

Java っぽさが増したので、特に Java の DI になじみのない LL からのユーザーは戸惑うかもしれません。 Runtime Dependency Injection の導入経緯やテストへの影響などは playframework-dev のメーリングリストを読むと面白いかと思います。

Play で Scalate を使う

play-scalate っていう名前のプラグインはいろんな人が書き散らかしてて github 検索するとボロボロ出てくるんですが、ついうっかり仲間に加わってしまいました。

https://github.com/tototoshi/play-scalate

build.sbt に設定すれば

libraryDependencies ++= Seq(
  "com.github.tototoshi" %% "play-scalate" % "0.1.0-SNAPSHOT",
  "org.scalatra.scalate" %% "scalate-core" % "1.7.0",
  "org.scala-lang" % "scala-compiler" % scalaVersion.value
)

unmanagedResourceDirectories in Compile += baseDirectory.value / "app" / "views"

こんな感じで使えます。

package controllers

import play.api._
import play.api.mvc._
import com.github.tototoshi.play2.scalate._

object Application extends Controller {

  def index = Action { implicit request =>
    Ok(Scalate.render("index.jade", Map("message" -> "hello")))
  }

}

ライブラリという形にして、SNAPSHOT を publish はしたけれど、そんな大したものではないのでいじりたければプロジェクト内にコピペして好きにいじってもらえばいいかなと思います。

Play で Scalate を使うときに気をつけることは、Dev モードで普通に動いてるのに Prod モードだと動かん!ってことにならないように、テンプレートファイルをファイルとしてではなくリソースとして読み込めってこどでしょうか。つまり、Play.api.current.getFile じゃなくて Play.api.current.resourceAsStream とか使おうってことです。Scalate はファイルを読み込むのを想定しているので、リソースを読み込むようにするのに一手間必要です。

Play ではリソースファイルは conf/ に置くことになってる(ちょっと気持ちわるい)ために app/views のファイルはリソースとして扱われないので、デプロイしたときにリソースが見つからん!というエラーも起きます。これを防ぐためにはテンプレートファイルは、app/views ではなく conf/views に置くか、conf 以下から app/viewsシンボリックリンクを...っていやまさか、

build.sbt で

unmanagedResourceDirectories in Compile += baseDirectory.value / "app" / "views"

とすれば良いです。

Play 2.4 と Dependency Injection

Play 2.3 が出たばっかで 2.4 の話をします。

前置き: Scala での DI

Scala では DI についてのベストプラクティスと言える方法はなく、まだ意見が分かれている状態です。大きく割ると DI コンテナなどを使った動的な DI と、cake pattern, implicit parameter, macro, reader monad などを利用した静的な DI の 2 パターンです。

動的な DI と静的な DI にはそれぞれメリットとデメリットがあります。動的な DI のメリットは Java のわりと優秀な DI コンテナ (Guice とか) が使えて比較的取っ付きやすいこと、デメリットは型安全ではないところ。静的な DI のメリットは型安全、デメリットは trait だの macro だの implicit parameter だの、コンパイル時間が伸びる要因満載であることです。

また静的な DI を行うとコードが少し複雑になってしまう傾向もあります。私は良く cake pattern で DI をしていますが、いくつもの trait を組み合わせてオブジェクトを組み立てていくと実装がばらけてしまい、コードが追いづらくなるという批判は納得しています。オブジェクトの継承関係が複雑になると、初期化順でハマったりもします。implicit parameter や reader monad による DI はあまりしませんが、まあ多くの開発者はそんなフレンドリーとは受け取ってくれないでしょう。

型安全好きな Scala コミュニティとしては当然静的な DI が良いよねと言いたいんでしょうが、現状はいろいろ問題があるので、DI は定期的によく盛り上がる(燃え上がる)話題の一つです。

Play 2.4 の DI

Play には DI の機能は一部あるものの、まだ本気ではない感じです。 そんな中 play-framework dev(開発者 ML) に、James Roper さんから DI の話題が投下されました。

Play 2.4 - Dependency Injection

要旨をまとめると

  • Play としては DI を動的に行うのか、静的に行うのか、どちらか一方の立場を取るつもりはない。ただしドキュメントに載せるなら動的な方法が良い気がする。
  • Play 2.4、そして Play 3.0 では constructor injection を基本にしたい
  • play.api.Play.current が状態を持たせるよりも constructor injection を基本にしたほうがテスタビリティは上がるだろう

constructor injection と言ってるのはまあこんな感じです。

class MyController(wsClient: WSClient) {
  wsClient.url(...)
}

constructor で WSClient という依存性を渡しています。まあ普通っすね。普通っぽいですが、これができると Play ではかなりテストがしやすくなります。

現状では Play の各モジュールは play.api.Application というグローバルな状態を持つクラスにべったりと依存しています。ピンとこない人に説明すると、良く何も考えずに import しているあれです。

import play.api.Play.current // これが play.api.Application のインスタンス

設定などアプリケーションの情報はこのインスタンスがなんでもかんでも持っています。そのため、Play のテストではこの Play.api.Application の Fake を作ってあげる必要があります。サードバーティプラグインを使ったり、データベースの設定を変えたり、ちょっと凝ったことをやりたくなるとこれがなかなかめんどくさい。

// Play のドキュメントから
class ExampleSpec extends PlaySpec with OneAppPerSuite {

  // Override app if you need a FakeApplication with other than
  // default parameters.
  implicit override lazy val app: FakeApplication =
    FakeApplication(
      additionalConfiguration = Map("ehcacheplugin" -> "disabled")
    )

  "The OneAppPerSuite trait" must {
    "provide a FakeApplication" in {
      app.configuration.getString("ehcacheplugin") mustBe Some("disabled")
    }
    "start the FakeApplication" in {
      Play.maybeApplication mustBe Some(app)
    }
  }
}

グローバルなオブジェクトに状態を持たせるのは、良くないなあと思いつつも、実際 Web アプリの場合はこういうグローバルなものがあったほうがやっぱり便利かなと思ってしまうこともあるのでよくわかりません。ただそういう設計ではやっぱりテストしづらいよね。もっとシンプルな Constructor Injection を基本にしたフレームワークにしてテストをもう少し楽に書けるようにしようね、というのが Play 2.4 そして Play 3.0 の方針だそうです。

実際どのような DI の方式が採用されるのか、どのくらいの規模の変更になるのかは知りませんが、Play 2.4、Play 3.0 のことを考えるならあまり play.api.Application(play.api.Play.current) に頼ったコードは書かないよう気をつけていたほうが移行が楽になるかもしれません。

いやあしかしすでにもう Play 3.0 の話題が出る時期なんですね。

Play で Scala.js を使う

Play meetup では Play や Scalaフレームワークの歴史みたいな話をしていましたが、本当はこのネタで LT をするつもりでした。諸事情によりってやつです。

さて Play で Scala.js をサポートしようぜって話は少し前からありますが、今のところまだ進展はないようです。

Add buildin ScalaJs support · Issue #2321 · playframework/playframework

ただ、もちろん sbt をいじれば一緒に使うことが可能です。

単純に optimizeJS して出力された js を Play の public/javascripts フォルダにコピーするとか。

lazy val copyJS = Command.command("copyJS") { (state) =>
  scala.sys.process.Process(List("cp", "js/target/scala-2.10/scala-js-slide-js-opt.js", "server/public/javascripts/")).!
  state
}

ちょっとやっつけ感ありますが問題なく使えます。

でもまあもうちょっとまともな方法はが公式サイトからリンク貼られてました。 これを clone するか設定パクればOKです。

git clone git@github.com:vmunier/play-with-scalajs-example.git

このプロジェクトでは Scala.js のプロジェクトが scalajs というサブプロジェクト、Play アプリが scalajvm というサブプロジェクト、さらにその両方で共有されるコードが shared というサブプロジェクトになっています。

├── README.md
├── project
│   ├── Build.scala
│   ├── build.properties
│   ├── plugins.sbt
│   ├── project
│   └── target
├── scala
│   ├── shared
│   └── target
├── scalajs
│   ├── src
│   └── target
└── scalajvm
    ├── app
    ├── conf
    ├── logs
    ├── public
    ├── target
    └── test

このやり方については pab_tech さんも書いてるのでそちらを見てください。

Scala.jsとJVMの両対応コードとScala.jsのテストの書き方 | PABlog

で、Play と使うための build.sbt 設定の中心は以下の部分です。 Scala.js の出力先を Play の public/javascripts にしています。さらに dependsOn を使って Play のアプリをコンパイルしたときに Scala.js のプロジェクトのコンパイルを走らせるようにしています。dist コマンドを使ったときには optimize も行うようになっています。

lazy val scalajvmSettings =
    play.Project.playScalaSettings ++ Seq(
      scalajsOutputDir     := (crossTarget in Compile).value / "classes" / "public" / "javascripts",
      compile in Compile <<= (compile in Compile) dependsOn (preoptimizeJS in (scalajs, Compile)),
      dist <<= dist dependsOn (optimizeJS in (scalajs, Compile)),
    ) ++ (
      // ask scalajs project to put its outputs in scalajsOutputDir
      Seq(packageExternalDepsJS, packageInternalDepsJS, packageExportedProductsJS, preoptimizeJS, optimizeJS) map { packageJSKey =>
        crossTarget in (scalajs, Compile, packageJSKey) := scalajsOutputDir.value
      }
    )

これで、Play でも Scala.js が使えるようになります。やったー!コンパイル時間がさらに伸びますね!

f:id:tototoshi:20140525232911p:plain

play-flyway 0.2.0 をリリースしました

Flyway の Play プラグイン、play-flyway の 0.2.0 をリリースしました。

https://github.com/tototoshi/play-flyway

インストール

Build.scala or build.sbt

libraryDependencies += "com.github.tototoshi" %% "play-flyway" % "0.2.0"

play.plugins

1000:com.github.tototoshi.play2.flyway.Plugin

主な変更内容

新たに管理ページのようなものを追加しました。/@flyway/${dbname} というパスにアクセスすると現在の DDL の適用状況を画面で確認することができます。また、ページ上部のリンクをクリックすることで migrate 以外の操作もできるようになりました。今回はとりあえず clean と init をつけてみました。


Evolutions に疲れたら一度お試しください。