Play Framework による API 開発 – ビルド定義入門

開発部 相田
はじめまして。開発部 SRE チームの相田です。この記事では JapanTaxi 内の Scala の技術情報について紹介します。

記事の要旨

  • Play を採用する場合は最初からマルチプロジェクト化しよう
  • 導入時は公式ドキュメントの記載を鵜呑みにしないように注意

デフォルト構成の確認

まずは 公式ドキュメント の手順に従って sbt new を走らせてみます。

$ sbt new playframework/play-scala-seed.g8

対話的にプロジェクトの情報を聞かれるので適当に答えておきましょう。

(ここでは nameorganization を変更しました。)

$ 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`)
    • hoge-play から hoge-domain を参照するための設定
    • 逆の参照はできない(依存が一方向になっている)ところがポイント

PlayScalaguice といった値は 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 現在の各種バージョン

ドキュメント一覧

JapanTaxiに興味を持ったら、まずはお話しませんか?