💁🏻 ※本記事は Mobility Technologies の前身である JapanTaxi 時代に公開していたもので、記事中での会社やサービスに関する記述は公開当時のものです。

記事の要旨
- Play を採用する場合は最初からマルチプロジェクト化しよう
- 導入時は公式ドキュメントの記載を鵜呑みにしないように注意
デフォルト構成の確認
まずは 公式ドキュメント の手順に従って sbt new を走らせてみます。
$ sbt new playframework/play-scala-seed.g8
対話的にプロジェクトの情報を聞かれるので適当に答えておきましょう。
(ここでは name と organization を変更しました。)
$ sbt new playframework/play-scala-seed.g8 This template generates a Play Scala project name [play-scala-seed]: hoge-play organization [com.example]: japantaxi play_version [2.6.12]: sbt_version [1.1.1]: scalatestplusplay_version [3.1.2]: Template applied <span class="pl-k">in</span> ./hoge-play</pre>
プロジェクトのファイル一式が自動で生成されます。
$ ls ./hoge-play | cat app build.gradle build.sbt conf gradle gradlew gradlew.bat project public test
これでもう Play によるアプリケーションの開発の準備は完了です。
めでたしめでたし…!と思いきや、話はそんなにうまくいきません (◞‸◟)
デフォルト構成の問題点
生成されたファイル群を確認してみます。
$ tree -I '.git|.idea|target' -a ./hoge-play
./hoge-play
├── .g8
│ └── form
│ ├── app
│ │ ├── controllers
│ │ │ └── $model__Camel$Controller.scala
│ │ └── views
│ │ └── $model__camel$
│ │ └── form.scala.html
│ ├── default.properties
│ └── test
│ └── controllers
│ └── $model__Camel$ControllerSpec.scala
├── .gitignore
├── app
│ ├── controllers
│ │ └── HomeController.scala
│ └── views
│ ├── index.scala.html
│ └── main.scala.html
├── build.gradle
├── build.sbt
├── conf
│ ├── application.conf
│ ├── logback.xml
│ ├── messages
│ └── routes
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── project
│ ├── build.properties
│ ├── plugins.sbt
│ └── scaffold.sbt
├── public
│ ├── images
│ │ └── favicon.png
│ ├── javascripts
│ │ └── main.js
│ └── stylesheets
│ └── main.css
└── test
└── controllers
└── HomeControllerSpec.scala
ここで生成されている hoge-play プロジェクトを見ると、残念ながら :
- API の開発には不要な大量のファイル群が生成される
- 拡張性・保守性に欠けたシングルプロジェクトで構成されている
という点で問題を抱えていることが分かります。もう少し細かく追ってみましょう。
不要なファイル群について
これらは全て API の開発には不要なファイルです。
- public 下のファイル群 (.js|.css|.png)
- app/views 下のファイル群 (.html)
- .g8 下の giter8 テンプレートファイル群
- sbt 以外のビルド定義にまつわるファイル群 (gradle*)
シングルプロジェクトの欠点について
以下のような要件を満たすことが極めて困難になります。
- モジュール同士の依存関係を管理したい
- 例 : model から controller を参照したらコンパイルエラーにしよう
- レイヤ設計を導入したい
- 例 : repository 層の上に service 層を追加しよう
- ライブラリとしてモジュールを切り出して開発したい
- 例 : 汎用的な〇〇処理だけ将来 OSS として公開しよう
- コンパイルやテストの単位を分割したい
- 例 : この〇〇部分はほとんど変更されないから事前にビルドしておこう
- 例 : この二つのモジュールは互いに独立しているから並列でテストしよう
- Play Framework への依存を最小限に留めておきたい
- 例 : 将来 Play から別なフレームワークに移行できるように作ろう
API 開発のために必要なプロジェクト構成
ということで解答編です。必要な最小構成はこのような形になります。
├── .gitignore
├── build.sbt
├── project
│ ├── HogeSettings.scala
│ ├── build.properties
│ └── plugins.sbt
├── hoge-domain
│ └── src/main/scala/japantaxi/hoge/domain
└── hoge-play
├── app
│ └── controllers
│ └── HelloController.scala
├── conf
│ ├── application.conf
│ └── routes
└── test
└── controllers
└── HelloControllerSpec.scala
それぞれのサブプロジェクトについては :
hoge-playには Play に依存したコードのみを配置する- controller や routes など
hoge-domainには Play とは無関係のドメインロジックを配置するhoge-playから参照されるモジュール群
という役割が想定されていますが、このあたりはチームやプロダクトによってはもっと細分化された構成が必要になるはずです。前項のような各種要件に応じて随時サブプロジェクトを追加してください。
マルチプロジェクト構成のビルド定義
- .sbt ファイルにも一工夫が必要です。コードを見てみましょう。
// build.sbt lazy val `hoge-play` = project. settings(HogeSettings.commons). settings(libraryDependencies ++= Seq( guice, "org.scalatestplus.play" %% "scalatestplus-play" % "3.1.2" % Test, )). enablePlugins(PlayScala). dependsOn(`hoge-domain`) lazy val `hoge-domain` = project. settings(HogeSettings.commons) lazy val root = Project("hoge-root", file("."))
ここで特に重要なのは以下二点です :
enablePlugins(PlayScala)- hoge-play 内で Play Framework を有効にするための設定
dependsOn(`hoge-domain`)
PlayScala や guice といった値は Play の sbt-plugin によって提供されています。
(突然どこからともなく現れたように見えますがご安心ください。)
// project/plugins.sbt addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.11")
HogeSettings の定義も同様に project の下です。
import sbt._ import Keys._ import sbt.Def.SettingList // project/HogeSettings.scala object HogeSettings { lazy val commons = new SettingList(Seq( // 言語のバージョン scalaVersion := "2.12.4", // ビルド時のコンパイラオプション scalacOptions ++= Seq( "-deprecation", "-feature", "-unchecked", "-Xfuture", ), // 依存ライブラリ libraryDependencies ++= Seq( "org.scalatest" %% "scalatest" % "3.0.4" % Test, ), )) }
サブプロジェクト間の共通設定は適宜 project の下に切り出していきましょう。
雛形からプロジェクトを生成する
ここまでの構成を手作業で用意するのはとても大変なので、自動生成のためのテンプレートを用意しています。
$ sbt new x7c1/play-api-seed.g8
コマンドライン引数から設定値を与えることも可能です。
$ sbt new x7c1/play-api-seed.g8 \ --name=fuga-api \ --organization=japantaxi \ --app_prefix=fuga
実行するとお馴染みのファイル群が生成されます。
$ tree -I '.git|.idea|target' -a ./fuga-api
./fuga-api
├── .gitignore
├── build.sbt
├── fuga-domain
│ └── src
│ └── main
│ └── scala
│ └── japantaxi
│ └── fuga
│ └── domain
│ └── Greeting.scala
├── fuga-play
│ ├── app
│ │ └── controllers
│ │ └── HelloController.scala
│ ├── conf
│ │ ├── application.conf
│ │ └── routes
│ └── test
│ └── controllers
│ └── HelloControllerSpec.scala
└── project
├── FugaSettings.scala
├── build.properties
└── plugins.sbt
sbt シェルの動作も確かめてみましょう。
$ cd ./fuga-api $ sbt [info] Loading settings from ... [info] ... [info] ... sbt:fuga-root> </pre> こんな感じでテストを走らせたり : <pre>sbt:fuga-root> fuga-play/test [info] Compiling 1 Scala source to ... [info] Done compiling. [info] ... [info] Passed: Total 1, Failed 0, Errors 0, Passed 1 [success] ...
もちろんローカルに開発サーバを立ち上げることもできます。
sbt:fuga-root> fuga-play/run ... [info] p.c.s.AkkaHttpServer - Listening for HTTP on /0:0:0:0:0:0:0:0:9000 ...
API を叩いてみるとレスポンスも確認できます。
$ curl http://localhost:9000/hello Hello, fuga-api!
まとめ
API 開発の導入の章はこれで終わりです。
- 仕事で作る場合は最初からマルチプロジェクト構成にしておきましょう。
- 複数のブランチで複数人が関わり始めたら途中で変更するのはほぼ不可能です。
- ウェブフレームワークへの依存は局所化しておくのが吉です。
- 実際 Play の過去のバージョンアップではしばしば後方互換が失われています。
おつかれさまでした!
参考リンク
2018-04-01 現在の各種バージョン
ドキュメント一覧
💁🏻 ※本記事は Mobility Technologies の前身である JapanTaxi 時代に公開していたもので、記事中での会社やサービスに関する記述は公開当時のものです。