Play framework(+ Slick)でのWebアプリの始め方

ちょっとしたアプリをAkkaベースで作ってみたくて、Play frameworkを試してみることに。
Playを使う上でSlickは鉄板の組み合わせと思ってたけど、結構イレギュラーな感じなのかな。
設定もデフォルトから変えないといけなかったし、Railsみたいにサクッと始めるとはいかなかったです。分かってしまえばどうということは無いでしょうが。

というわけで備忘録がてらチュートリアルを書いてみました。
基本的には、takuya0301さんの「[Play 2.5 と Slick 3.1 のチュートリアル](http://qiita.com/takuya0301/items/5f3e535bb00a208699a2)」の記事のまんまなのですが、その通りにやってもうまくいかなかったところがバージョンのせいかあったので対応しています。

プロジェクトの作成
-------------------------

Playの始め方はsbtを書くとかgiter8とかIntelliJで始めるとか色々あります。
何となく、ひな形は[Activator](http://www.lightbend.com/activator/download)から始めるのが楽な気がするので、Activatorベースで進めます。

というわけで、まずはActivatorをインストールして環境変数にパスを通します。Macならbrewで入るみたいですね。
Windowsなら普通にダウンロードして環境変数にパス通すでOKです。

続いて下記コマンドでひな形を作成。

```bash
$ activator new example-play play-scala
$ cd example-play
```

これでひな形は完成。とりあえず起動してみましょう。この辺はRailsっぽいですね。

activator-uiから実行する手もあるけど、今回はコマンドベースで。

```bash
$ activator
$ [example-play] # ここからactivator上での作業
$ [example-play] run

--- (Running the application, auto-reloading is enabled) ---

[info] p.c.s.NettyServer - Listening for HTTP on /0:0:0:0:0:0:0:0:9000

(Server started, use Ctrl+D to stop and go back to the console...)
```

という分けでブラウザで`http://localhost:9000`にアクセスしてみましょう。下記のような画面になってれば成功です。
なお、コンパイルとかしてるので初回アクセスは少し時間がかかります。

![01.png](https://qiita-image-store.s3.amazonaws.com/0/54904/8a42b490-cdb4-9405-37e2-2e217bac21f6.png)

`Ctrl+D`でプロセスを停止できるので、停止しておきます。

設定ファイルの修正
----------------------------

### build.sbtの修正

ひな形が無事動いたので、build.sbtにslick向けの設定を追加します。修正前後の差分は下記の通り

```diff:build.sbt
@@ -7,9 +7,11 @@ lazy val root = (project in file(".")).enablePlugins(PlayScala)
 scalaVersion := "2.11.7"

 libraryDependencies ++= Seq(
-  jdbc,
   cache,
   ws,
-  "org.scalatestplus.play" %% "scalatestplus-play" % "1.5.1" % Test
+  "org.scalatestplus.play" %% "scalatestplus-play" % "1.5.1" % Test,
+  "com.typesafe.play" %% "play-slick" % "2.0.0",
+  "com.typesafe.play" %% "play-slick-evolutions" % "2.0.0",
+  "com.h2database" % "h2" % "1.3.176"
 )
```

Slickは通常のJDBCとは異なるアクセスをするので、jdbcモジュールを外す必要があります。
evolutionsというのはRailsのようにSQLを管理するJavaのライブラリです。
また、今回は[公式チュートリアル](https://www.playframework.com/documentation/2.5.x/PlaySlick)と同じくH2をデータベースとして使う予定なので、H2を設定してあります。

### application.confの修正

[公式](https://www.playframework.com/documentation/2.5.x/PlaySlick)にも記載あがある通り、Slickを使う場合は、JDBCでは無いせいかDB周りの設定は`slick.dbs.default`を使う必要あります。
なので、ごそっと書きのように入れ替えました。

```diff:conf/application.conf
@@ -303,51 +303,14 @@ play.evolutions {
   #db.default.enabled = false
 }

-## Database Connection Pool
-# https://www.playframework.com/documentation/latest/SettingsJDBC
-# ~~~~~
-# Play doesn't require a JDBC database to run, but you can easily enable one.
-#
-# libraryDependencies += jdbc
-#
-play.db {
-  # The combination of these two settings results in "db.default" as the
-  # default JDBC pool:
-  #config = "db"
-  #default = "default"
-
-  # Play uses HikariCP as the default connection pool.  You can override
-  # settings by changing the prototype:
-  prototype {
-    # Sets a fixed JDBC connection pool size of 50
-    #hikaricp.minimumIdle = 50
-    #hikaricp.maximumPoolSize = 50
-  }
-}
-
-## JDBC Datasource
-# https://www.playframework.com/documentation/latest/JavaDatabase
+## Datasource
 # https://www.playframework.com/documentation/latest/ScalaDatabase
 # ~~~~~
-# Once JDBC datasource is set up, you can work with several different
-# database options:
 #
 # Slick (Scala preferred option): https://www.playframework.com/documentation/latest/PlaySlick
-# JPA (Java preferred option): https://playframework.com/documentation/latest/JavaJPA
-# EBean: https://playframework.com/documentation/latest/JavaEbean
-# Anorm: https://www.playframework.com/documentation/latest/ScalaAnorm
-#
-db {
-  # You can declare as many datasources as you want.
-  # By convention, the default datasource is named `default`
-
-  # https://www.playframework.com/documentation/latest/Developing-with-the-H2-Database
-  #default.driver = org.h2.Driver
-  #default.url = "jdbc:h2:mem:play"
-  #default.username = sa
-  #default.password = ""
-
-  # You can turn on SQL logging for any datasource
-  # https://www.playframework.com/documentation/latest/Highlights25#Logging-SQL-statements
-  #default.logSql=true
-}
+
+slick.dbs.default.driver="slick.driver.H2Driver$"
+slick.dbs.default.db.driver="org.h2.Driver"
+slick.dbs.default.db.url="jdbc:h2:mem:play;DB_CLOSE_DELAY=-1"
+slick.dbs.default.db.user=sa
+slick.dbs.default.db.password=""
```

ポイントは`DB_CLOSE_DELAY=-1`で、これを指定しないと、後で説明する`Apply this script now!`ボタンを押しても、ウンともスンとも言わなかったので要注意です。

アプリケーション機能の追加
----------------------------

設定が終わったので、アプリケーション機能を追加します。
Railsだとこの辺もGeneratorである程度よしにする感じですが、Playは手書き文化っぽい？　求ム情報。

このあたりは[takuya0301さんの記事](http://qiita.com/takuya0301/items/5f3e535bb00a208699a2)をたんたんと写経。解説とかもそちらが参考になると思うので読んでみてください。

### SQLの追加

evalutionむけにはdefault配下に一定のルールでSQLを書けばいいみたい。
`1.sql`は下記のようにCREATEとDROP時のものを書きます

```sql:conf/evolutions/default/1.sql
# --- !Ups

CREATE TABLE "CAT" (
    "NAME" VARCHAR NOT NULL PRIMARY KEY,
    "COLOR" VARCHAR NOT NULL
);

# --- !Downs

DROP TABLE "CAT";
```

### DTOとDAO

つづいてDTO。これはcase classでさくりと。

```java:app/models/Models.scala
package models

case class Cat(name: String, color: String)
```

DAOも作ります。

```java:app/dao/CatDAO.scala
package dao

import scala.concurrent.Future

import javax.inject.Inject
import models.Cat
import play.api.db.slick.DatabaseConfigProvider
import play.api.db.slick.HasDatabaseConfigProvider
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import slick.driver.JdbcProfile

class CatDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvider) extends HasDatabaseConfigProvider[JdbcProfile] {
  import driver.api._

  private val Cats = TableQuery[CatsTable]

  def all(): Future[Seq[Cat]] = db.run(Cats.result)

  def insert(cat: Cat): Future[Unit] = db.run(Cats += cat).map { _ => () }

  private class CatsTable(tag: Tag) extends Table[Cat](tag, "CAT") {

    def name = column[String]("NAME", O.PrimaryKey)
    def color = column[String]("COLOR")

    def * = (name, color) <> (Cat.tupled, Cat.unapply _)
  }
}
```

### ViewとController

表題の通りですが、まずはルーティング設定を変えるために`conf/routes`を修正。

```diff:conf/routes
 # An example controller showing a sample home page
 GET     /                           controllers.HomeController.index
+POST    /insert/cat                 controllers.HomeController.insertCat
 # An example controller showing how to use dependency injection
 GET     /count                      controllers.CountController.count
 # An example controller showing how to write asynchronous code
```

つづいて、Controller。これは既存にある`/app/controllers/HomeController.scala`を修正します。

```diff:/app/controllers/HomeController.scala
@@ -3,22 +3,36 @@ package controllers
 import javax.inject._
 import play.api._
 import play.api.mvc._
+import play.api.db._
+import dao.CatDAO
+import models.Cat
+import play.api.data.Form
+import play.api.data.Forms.mapping
+import play.api.data.Forms.text
+import play.api.libs.concurrent.Execution.Implicits.defaultContext

 @Singleton
-class HomeController @Inject() extends Controller {
+class HomeController @Inject()(catDao: CatDAO) extends Controller {

-  def index = Action {
-    Ok(views.html.index("Your new application is ready."))
+  def index = Action.async {
+    catDao.all().map {
+      cats => Ok(views.html.index(cats))
+    }
   }

+  def insertCat = Action.async { implicit request =>
+    val cat: Cat = catForm.bindFromRequest.get
+    catDao.insert(cat).map(_ => Redirect(routes.HomeController.index))
+  }
+
+  val catForm = Form(
+    mapping(
+      "name" -> text(),
+      "color" -> text()
+    )(Cat.apply)(Cat.unapply)
+  )
 }
```

これに合わせて、`index.scala.html`も修正。

```diff:views/index.scala.html
@@ -1,20 +1,23 @@
-@*
- * This template takes a single argument, a String containing a
- * message to display.
- *@
-@(message: String)
+@(cats: Seq[Cat])

-@*
- * Call the `main` template with two arguments. The first
- * argument is a `String` with the title of the page, the second
- * argument is an `Html` object containing the body of the page.
- *@
-@main("Welcome to Play") {
+@main("Cat database") {
+<div>
+ <div id="cats">
+  <h2>Insert a kitty cat here:</h2>

-    @*
-     * Get an `Html` object by calling the built-in Play welcome
-     * template and passing a `String` message.
-     *@
-    @play20.welcome(message, style = "Scala")
+  <form action="/insert/cat" method="POST">
+   <input name="name" type="text" placeholder="name your feline friend"/>
+   <input name="color" type="text" placeholder="enter the color of this kitty cat"/>
+   <input type="submit"/>
+  </form>

-}
+  <h2>Previously inserted cats:</h2>
+  <table>
+   <tr><th>Name</th><th>Color</th></tr>
+   @for(c <- cats){
+   <tr><td>@c.name</td><td>@c.color</td></tr>
+   }
+  </table>
+ </div>
+</div>
+}
```

アプリケーションの起動
-------------------

では、アプリケーションを起動します。

```bash
[example-play] $ run

--- (Running the application, auto-reloading is enabled) ---

[info] p.c.s.NettyServer - Listening for HTTP on /0:0:0:0:0:0:0:0:9000

(Server started, use Ctrl+D to stop and go back to the console...)
```

`http://localhost:9000` にアクセスすると下記のようなエラー画面になります。

![02.png](https://qiita-image-store.s3.amazonaws.com/0/54904/30076140-7da5-4e34-cda7-80e9a8bd6033.png)

ようは、使おうとしているテーブルが無いよ！　ってエラーですね。赤字の`Apply this script now!`を押して適用してやりましょう。
そうすると、下記のような画面になるはずです。

![03.png](https://qiita-image-store.s3.amazonaws.com/0/54904/a6371306-49cb-a3e1-dfed-97f485b8dc7c.png)

これで成功ですね。もし、`Apply this script now!`を押しても画面が変わらずDB変更がされてなければ、`DB_CLOSE_DELAY=-1`が抜けているかもしれないのでapplication.confを見直してください。

デバックモードでの起動
-------------------

最後におまけでデバッグモードでの起動の仕方です。

```bash
$ activator debug run -jvm-debug 8888
```

`run`の前に`debug`コマンドを書いておくことでログレベルがデバッグになります。
jvm-debugはデバッグ用のポートをしていすることになるので、こちらにIDE等からアタッチすればデバッグが可能です。

まとめ
--------------

おもったより手こずりましたが、無事Play + Slickの開発準備が整いました。この辺がRails並みに整備されるともっといいのですが。

まあ、微妙に罠っぽい挙動だったところもあるので、そのうち治ると予想。
Playの非同期性を活かすならSlick3がやはり一番だろうし、本質に行くまでの躓きは減っておいてほしいなー。

それでは、Happy Hacking!

参考
-------------------

- [Play 2.5 と Slick 3.1 のチュートリアル](http://qiita.com/takuya0301/items/5f3e535bb00a208699a2)
- [Using Play Slick](https://www.playframework.com/documentation/2.5.x/PlaySlick)
- [Developing-with-the-H2-Database](https://github.com/playframework/playframework/blob/master/documentation/manual/working/commonGuide/database/Developing-with-the-H2-Database.md)