Blog
Play + SBTでプロジェクト分割管理
どうも。GameTailorの松川です。
GameTailorではPlayFrameworkを利用したWebアプリケーションや,
Elastic MapReduceを利用したBatch アプリケーション等、
複数のアプリケーションから同一のDomain層、Infrastructure層、Utilsの実装、共通設定を使いまわせるように
SBTのマルチプロジェクトを利用してます。
今回は簡単にAppからUtilsの実装を利用するマルチプロジェクトを作成しながら、GameTailorでの管理方法をご紹介します。
1. Rootプロジェクト作成
複数のプロジェクトを統括管理するrootプロジェクトを作成します。
1-1. rootプロジェクト作成
1 2 |
$ mkdir -p sbt-multiple-project-example/project $ cd sbt-multiple-project-example |
2-1. PlayPluginの設定を追加
rootプロジェクトからPlayのサブプロジェクトを定義できるようplay-pluginの設定を追加
1 |
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.4.0") |
2. Web App実装
rootプロジェクトのモジュールとして”Hello play app!”と出力するだけの簡単なPlayアプリケーションを作成していきます。
2-1.play application作成
1 2 3 4 |
$ pwd ~/sbt-multiple-project-example $ mkdir modules && cd $_ $ activator new hello-play-app play-scala |
2-2.HelloPlayApp実装
1 2 3 4 5 6 7 8 9 10 11 12 |
package controllers import play.api._ import play.api.mvc._ object HelloPlayApp extends Controller { def index = Action { Ok("Hello play app!") } } |
2-3.routesへ設定追加
1 |
$ echo 'GET /hello-play-app controllers.HelloPlayApp.index' >> hello-play-app/conf/routes |
2-4.動作確認
1 2 |
$ cd hello-play-app $ sbt run |
1 2 |
$ curl http://localhost:9000/hello-play-app ## 別ウィンドウで実行 Hello play app! |
3. HelloPlayAppのbuild設定をrootプロジェクトのbuild.sbtへ記述
続いて、modules/hello-play-appをrootプロジェクトから管理するよう設定を入れていきます
3-1. build.sbt設定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
name := """sbt-multiple-project-example""" version := "1.0-SNAPSHOT" lazy val root = (project in file(".")) lazy val helloPlayApp = (project in file("modules/hello-play-app")) .settings(libraryDependencies ++= Seq(jdbc,cache,ws,specs2 % Test)) .enablePlugins(PlayScala) scalaVersion := "2.11.6" resolvers += "scalaz-bintray" at "http://dl.bintray.com/scalaz/releases" // Play provides two styles of routers, one expects its actions to be injected, the // other, legacy style, accesses its actions statically. routesGenerator := InjectedRoutesGenerator |
※ここではbuild.sbtに記述していますが、.scalaファイルに定義する方法もあります。細かい内容は公式へ譲るとして、gameTailorでは長くなった設定(Dependencisや共通の基本設定)を.scalaへ記述し、build.sbtへimportして利用しています。
3-2. HelloAppのbuild.sbtを削除
rootプロジェクトで管理するのでここのbuild.sbtは削除しておきます。
1 |
$ rm ~/sbt-multiple-project-example/modules/hello-play-app/build.sbt |
4. HelloUtills実装
続いて、HelloPlayAppから利用するHelloUtilsを実装します。
ここは非Playで実装してきます
1 |
$ mkdir -p hello-utils/src/main/scala && cd $_ |
pimp my libraryで現在時間の文字列を末尾に追加して返すメソッドを実装
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package utils import org.joda.time.DateTime case class RichString(s: String) { def addNowDateTime(): String = s ++ DateTime.now.toString("yyyy-mm-dd hh:mm:ss.sss") } object RichString { implicit def stringToRich(s: String): RichString = RichString(s) } |
5. マルチプロジェクト化
5-1. HelloAppにHelloUtilsを依存させる
dependsOnで依存関係を持たせることができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
name := """sbt-multiple-project-example""" version := "1.0-SNAPSHOT" lazy val commonSettings = Seq(scalaVersion := "2.11.6") lazy val root = project in file(".") lazy val helloPlayApp = (project in file("modules/hello-play-app")) .settings(commonSettings: _*) .settings(libraryDependencies ++= Seq(jdbc,cache,ws,specs2 % Test)) .settings(resolvers += "scalaz-bintray" at "http://dl.bintray.com/scalaz/releases") .dependsOn(helloUtils) .enablePlugins(PlayScala) lazy val helloUtils = (project in file("modules/hello-utils")) .settings(commonSettings: _*) .settings(libraryDependencies ++= Seq( "joda-time" % "joda-time" % "2.8", "org.joda" % "joda-convert" % "1.7" )) // Play provides two styles of routers, one expects its actions to be injected, the // other, legacy style, accesses its actions statically. routesGenerator := InjectedRoutesGenerator |
※dependsOnした子プロジェクトから親プロジェクトへの参照はできません。
これを利用し、GameTailorでは下位のプロジェクトが上位のプロジェクトへ依存することを制限しています。
GameTailorプロジェクト全体図
5-2. helloPlayAppにhelloUtilsを利用する実装を追加
1 2 3 4 5 6 7 8 9 10 11 12 |
package controllers import play.api.mvc._ import utils.RichString._ class HelloApp extends Controller { def index = Action { Ok("Hello play app! ".addNowDateTime()) } } |
5-3. 動作確認
helloPlayAppプロジェクトを指定してrunする必要があります
5-3-1. sbtのインタラクティブモードでプロジェクト一覧を確認
1 2 3 4 5 6 |
$ sbt > projects [info] In file:~/sbt-multiple-project-example/ [info] helloPlayApp [info] helloUtils [info] * root |
5-3-2. helloPlayApp プロジェクトを選択し、runを実行
1 2 3 |
> project helloPlayApp [info] Set current project to hello-play-app (in build file:~/sbt-multiple-project-example/) [hello-play-app] $ run |
※sbt ‘project helloPlayApp’ runでも可
5-3-3. 実行
1 2 |
$ curl http://localhost:9000/hello-play-app ## 別ウィンドウから実行 Hello play app! 2015-07-05 05:16:32.032% |
ここまでで簡単にですが、play プロジェクトをマルチプロジェクト構成で利用してみました。
コード→https://github.com/ma2k8/sbt-multiple-project-example
コードを簡単に共有することができるので、インフラストラクチャ層、ドメイン層のコードを別プロジェクトとして管理すると捗ると思います。
別プロジェクトのコードもPlayに依存させて使うこともでき、その場合はenablePluginするだけです。
1 2 |
lazy val helloUtils = (project in file("modules/hello-utils")) .enablePlugins(PlayScala) |
6.Test
testはjavaOptionsでconfを指定する設定を.settingsでプロジェクトに定義しています。
1 2 |
val appTestSettings = javaOptions in Test ++= Seq("-Dconfig.file=conf/application.test.conf") val libTestSettings = javaOptions in Test ++= Seq("-Dconfig.file=../common/conf/common.test.conf") |
7. Deploy
GameTailorではプロジェクトごとにgitで管理しており、rootプロジェクトからgit subtreeで紐付けています。
subtreeによって以下のフローでリポジトリ個別のデプロイを行っています。
1 |
rootPjへコミット -> jenkinsへhook -> test -> git subtree push -> 各リポジトリでhook -> JenkinsでDeploy |
※subtree pushは変更のあったファイルに属するリポジトリにコミット内容をpushしてくれるので、各リポジトリごとにhookすることができます。
こうすることにより、rootPjを管理するリポジトリへのみコミットすれば良いので開発フローをシンプルに保つことができます。
8. プロジェクトごとに違うScalaバージョンでビルドしたい場合
GameTailorではSpark on Emrのバッチもマルチプロジェクトで管理しています。
Sparkの推奨scala versionが2.10系なので2.11系のプロジェクトとバッティングしてしまいます。。
これはsbt実行時のscalaVersion指定でjar化し、2.10系でビルドできないコードはmergeStorategyで省くことで解決することが出来ました。sbt素晴らしいですね。
1 |
$ sbt 'project emrBatch' ++2.10.5 assembly |
駆け足でGameTailorでのプロジェクト管理方法について紹介させていただきました。
プロジェクトはムダのない構成のほうが愛着も湧きますし、実装も熱が入ります。少しでも皆様の参考になれば嬉しいです。
それでは。
Copyright about logo
Copyright (c) 2002-2015 EPFL
Author