PlayFrameworkのサブプロジェクトではまったこと


はまったこと

PlayFramework2.3.x系でのサブプロジェクトの設定ではまっことは以下2点

  • テスト時の設定ファイルの読込
  • routesの設定

やりかた

基本的にはPlayFrameworkの公式ドキュメント通りに設定します。
サブプロジェクト
公式ドキュメントでは2.2.xではBuild.scalaで全て設定していますが、2.3.xではbuild.sbtとBuild.scalaの両方に適宜設定する方法に変更されているようです。
Build.scalaのみでも記述は可能だと思いますが、本記事では公式ドキュメント通りの設定でハマったことを記述します。

設定してみる

今回はDDDっぽい構成にプラス、静的ファイルのみのプロジェクトもサブプロジェクトとして分けています。

メインプロジェクトの設定

conf/build.sbt
name := """multi-project-sample"""

version := "1.0-SNAPSHOT"

lazy val asset = (project in file("modules/asset"))
  .enablePlugins(PlayScala)

lazy val infrastructure = (project in file("modules/infrastructure"))
  .enablePlugins(PlayScala)

lazy val domain = (project in file("modules/domain"))
  .enablePlugins(PlayScala)
  .dependsOn(infrastructure)

lazy val application = (project in file("modules/application"))
  .enablePlugins(PlayScala)
  .dependsOn(infrastructure, domain)

lazy val main = (project in file("."))
  .enablePlugins(PlayScala)
  .dependsOn(asset, infrastructure, domain, application)
  .aggregate(asset, infrastructure, domain, application)

Setting.main

サブプロジェクトごとの設定

modules/xxx/build.sbt
name := """multi-project-xxx"""

version := "1.0-SNAPSHOT"

Setting.application

プロジェクトの詳細設定

はまったこと1

javaOptions in Test ++= Seq("-Dconfig.file=../../conf/application.conf")

サブプロジェクトでテストする時に、application.confを参照しているとそのままではパスの設定が違うため、内容を取得することができません。
run、stageされたものはプロジェクトが集約されているのでconf/application.confを参照できますが、test時には個別のプロジェクトとして実行されるのでmodule/xxx/conf/application.confのパスを参照してしまいます。
とりあえず上記の設定を入れていますが、いけてません!!

project/Build.scala
import sbt._
import Keys._
import scoverage._

object Setting {
  val common: Seq[Setting[_]] = Seq(
    organization := "com.example",
    scalaVersion := "2.11.5",
    libraryDependencies ++= Dependency.common,
    
    scalacOptions ++= Seq("-deprecation", "-feature", "-language:reflectiveCalls"),
    
    )

  val asset = common ++ Seq(
    resolvers ++= Resolver.common,
    libraryDependencies ++= Dependency.asset,
    javaOptions in Test ++= Seq("-Dconfig.file=../../conf/application.conf")
  )
  val infrastructure = common ++ Seq(
    resolvers ++= Resolver.infrastructure,
    libraryDependencies ++= Dependency.infrastructure,
    javaOptions in Test ++= Seq("-Dconfig.file=../../conf/application.conf")
  )
  val domain = common ++ Seq(
    resolvers ++= Resolver.domain,
    libraryDependencies ++= Dependency.domain,
    javaOptions in Test ++= Seq("-Dconfig.file=../../conf/application.conf")
  )
  val application = common ++ Seq(
    resolvers ++= Resolver.application,
    libraryDependencies ++= Dependency.application,
    javaOptions in Test ++= Seq("-Dconfig.file=../../conf/application.conf")
  )
  val main = common ++ Seq(
    resolvers ++= Resolver.main,
    libraryDependencies ++= Dependency.main
  )
}

object Dependency {
  val common = Seq()

  val asset = common ++ Seq()
  val infrastructure = common ++ Seq()
  val domain = common ++ Seq()
  val application = common ++ Seq()
  val main = common ++ Seq()
}

object Resolver {
  val common = Seq()

  val asset = common ++ Seq()
  val infrastructure = common ++ Seq()
  val domain = common ++ Seq()
  val application = common ++ Seq()
  val main = common ++ Seq()
}

routesの分割

routesファイルは集約された後にユニークなファイルになっている必要があるので、サブプロジェクトごとにroutesファイル名を変更してメインプロジェクトのroutesから参照するように設定します。
パスの設定部分は、ユニークであれば適当な名前で良いっぽいです。

conf/routes
GET     /                   controllers.Application.index
->      /asset              asset.Routesh
->      /application        application.Routes
GET     /assets/*file       controllers.Assets.at(path="/public", file)

はまったこと2
公式ドキュメントに記載してあるのでハマったのは私だけでしょう…
サブプロジェクトでのroutesでもAssetsクラスを使用したい場合は、継承したクラスを用意する必要があります。
これはルーティングクラスを生成する際に名前がユニークではないと、クラス名が重複するからからじゃないかなー?たぶん

modules/asset/app/controllers/Assets.scala
package controllers.asset
object Assets extends controllers.AssetsBuilder
modules/asset/conf/asset.routes
GET /assets/*file           controllers.asset.Assets.at(path="/public", file)

うごかしてみる

testコマンド!!

[multi-project-sample] $ test
[info] ApplicationSpec
[info]
[info] Application should
[info] + with configuration
[info]
[info] Total for specification ApplicationSpec
[info] Finished in 1 second, 49 ms
[info] 1 example, 0 failure, 0 error
[info] Passed: Total 1, Failed 0, Errors 0, Passed 1
[info] ApplicationSpec
[info]
[info] Application should
[info] + send 404 on a bad request
[info] + render the index page
[info]
[info] Total for specification ApplicationSpec
[info] Finished in 1 second, 217 ms
[info] 2 examples, 0 failure, 0 error
[info] IntegrationSpec
[info]
[info] Application should
[info] + work from within a browser
[info]
[info] Total for specification IntegrationSpec
[info] Finished in 1 second, 611 ms
[info] 1 example, 0 failure, 0 error
[info] Passed: Total 3, Failed 0, Errors 0, Passed 3
[success] Total time: 10 s, completed 2015/02/11 18:18:14

Scoverrage!!

あ、設定は別途必要です。
coverageAggregateすることでレポートを集約することができます。

$ activator clean coverage test coverageAggregate
$ open target/scala-2.11/scoverage-report/index.html

画像の取得確認!!

後述するGithubのプロジェクトで、以下コマンドを実行すると、サブプロジェクトとメインプロジェクトに配置された画像がそれぞれ正しく取得できることがわかると思います。

$ wget http://127.0.0.1:9000/assets/scala-chan.png
$ wget http://127.0.0.1:9000/assets/mysql-chan.png

さいごに

GitHubに動作するサンプルをPushしてあります。
play2.3-multi-project-sample