Play Frameworkについてざっくり説明する

普段、Play Framework、Struts、Seasar2、JSF2.0で仕事をしている私です。Playが流行り始めてからだいぶ経ちます。私もPlayに触れたのは最近の事で、黎明期から使っていたわけではありませんが、私と同じようにそろそろPlayもやってみようかなというearly majority(たぶん)の人が増えてくるころではないかと思っています。

私はPlayを触り始めて3~4か月たちますが、最初に思っていた疑問に対する答えがあらかた分かってきたのでその疑問と答えを忘れないうちにここに記します。また、こういうふうな説明があればもっと理解が早かったなあなどと思うようなところがあればそれも書いてみます。間違いがありましたらご指摘いただけると助かります。

導入編

結局Playって何なのよ

新種のWebフレームワークに触れるとき、まずそのフレームワークがどういうモデルで動いているかということから調べる方は多いと思います。ググると、PlayはMVCフレームワークだと出てきます。実際、そうだと思いますし、playのライブラリの名前空間にもmvcとか名前が付いていたりします。ただ一般的なMVCフレームワークとは少し異なっていて、MVCフレームワークを想像しながらコーディングすると違和感があると思います。

私がPlayとは何か、と聞かれて簡潔にするならば、以下のような感じです。

以下の3つから構成されるフレームワーク。

  • HTTPレスポンスを返却する関数F
  • URLと関数Fをマッピングする機能
  • 動的なHTMLを吐き出す関数を簡単に書ける機能

Playの主要な構成要素は3つです。

1. routesファイル。URLとそれを処理する関数(コントローラ)を結び付けます。URLの一部は引数として処理し、関数に渡すこともできます。専用の書式があり、コードに変換されたのちバイナリコードに落ちます。

2. アクション(コントローラ)。受け取った引数を元にHTTPレスポンスを構築して返却する関数です。JavaもしくはScalaで実装します。レスポンスはHTMLでもバイナリストリームでもJSONでもXMLでもなんでも良いです。

3. ビュー。任意の引数を受け取り、それを元にHTMLを動的に構築して返却する関数です。HTMLコードとJavaもしくはScalaコードがミックスしたような書式になっており、コンパイルしてバイナリコードになります。この関数は普通、アクションから使われます。アクションは表示に必要な任意のデータ構造を作成し、それをビュー(関数)に与え、生成されたHTMLをHTTPレスポンスとして書き出します。

ここで重要なのはroutesもビューも最終的にコードに変換された後、コンパイルされてバイナリコードに何らかの関数とかオブジェクトの形で実装されるという事です。あるURLが要求されたらそれに対応するアクション関数(コントローラ)が実行され、必要に応じてそのなかでHTMLを返すような関数(ビュー)が呼ばれます。

モデルは特にこれを使わなければならないというのはありません。Playを使う人が任意に決めて良いです。また、コントローラとビューの間でやり取りされる情報はいわゆるPOJOのようなもので、特殊なクラスを継承したり、特殊なアノテーションを加えたりなどといった必要は一切ありません。

JavaかScalaか

PlayはJavaもしくはScalaが使えます。私はもっぱらScalaをお勧めします。それはJavaに比べてScalaの方が優れている点が多いからです。

まずラムダ式、高階関数、マルチパラダイム、型推論などの近代的な言語であれば備わっていて当然の機能/思想が取り入れられています。Java 8と比較しても表記が簡潔で分かりやすいと個人的には思います。中でも特に強力なのはパターンマッチでしょう。パターンマッチを最大限活用すれば回りくどいif-elseやswitch文のほとんどを簡略化してパッと見で理解できるコードを記述できます。私はもうほとんどif文を書いていません。

また、Scalaの型推論は驚くべき頭の良さで、高階関数を繋げてかなり複雑なコードを書いてもしっかりと推論してくれます。また、「スケーラブル」の名前に偽りは無く、ちょっとしたコードを書くのも大規模なプログラムを書くのもどちらにも適した言語になっています。Scalaを覚えるまでは「Pythonで書こうかな」と思っていたようなコードも最近はScalaで書いています。ライブラリの充実度はさすがにPythonには敵わないですが…。

唯一私がJavaに比べてScalaが明確に劣ると感じるのはコンパイル速度です。Core 2 Duoあたりのマシンでコードを組むとそれなりに気になる遅さです。新しめのスペックのPCであればそれほど気になりません。もっとも、コンパイル速度は遅くとも生産性の向上分で十分すぎるほどペイできるのですが。

また、JVMで動くの既存のJava資産も応用可能です。Scala側にもJavaオブジェクトとの自動変換の仕組みが備わっていて書くのもそれほどしんどくありません。また、PlayではScalaとJavaのコードが共存できるのでScalaで始めることへのリスクや敷居も十分に低いと思います。

以下ページなども参照ください。

以降では、Scalaを用いることを前提にした記述になります。

ドキュメントはどれ見ればいいの

この記事書いた時点での最新版(2.3)のドキュメントは以下です。

Play 2.3.x documentation

一応日本語で見れますが、あまりメンテナンスされていないらしく英語が残っていたり、所々表示されない部分があったりします。少し古いバージョンだといい感じに翻訳されていますが、すると今度は最新版では通用しない情報だったりしますので面倒くさがらず最新の英語ドキュメントを読むのが良いです。

Playコマンドなんて無いんだけど

Playは最近Typesafe Activatorでの配布に移行しました。PlayについてググるとPlayコマンドを使うとかいうページが多数ヒットしますが、ActivatorではPlayコマンドはありません。が、「play」を「activator」に置き換えれば大体動きます。

例: play run → activator run

もし引数が二つ以上になる場合は""で囲んでください。

例: activator "compile hoge"

Scalaの記法が省略しすぎで意味不明なんだけど

複雑なようで一定の規則があるので数日で慣れます。慣れたら知らなくても「多分こうだろう」と想像して省略した記法を書けるようになります。

以下の記事が非常によくまとまっていますので、一度読んでおくと理解が早いと思います。

Scala の省略ルール早覚え

O/Rマッパは何を使えばいいの?

Scalaとの組み合わせで良く使われるのはanormかSlickです。

Anorm によるシンプルな SQL データアクセス

Slick

PlayではAnormをメインにサポートしていますが、いずれSlickに置き換わるとの事です。ちなみに、Slickを管理しているのはTypesafeです。

Anormは厳密にはO/Rマッパではありません(Anorm is Not O/R Mapperの再帰頭字語)。Anormはクエリを表現するのにSQL文を使用して、取り出した結果をどうオブジェクトの世界にマッピングするか、という定義を書きます。EntityとかDaoみたいなものはありません(マップするオブジェクトをDaoなどと表現しているWebサイトはたまに見ます)。アノテーションも要りません。覚えることが少なく、シンプルで使いやすいです。

Slickの思想はAnormとは真逆で、何でもやってくれるようなフレームワークです。コードファーストでコードからデータベース定義を生成することもできますし、データベース定義からマッパーオブジェクトを自動生成することもできます。クエリも抽象化されてDSLっぽく書けます。fluentなインタフェースなどでも書けます。が、たとえばEntity FrameworkやJavaEEのEntityManagerのように特定のオブジェクトの変更追跡をするとかいうお節介なことはしません。

AnormとSlickは一長一短だと思います。Slickはまだ開発途上ということもあり、パフォーマンスの面で問題があるような気がします。私の使い方が悪いのか、updateやdelete操作が極端に遅いシーンが多々あります。ただ、Slickは生のSQL文も流せるのでそれほど神経質に気にしなくても良いかもしれません。

warでデプロイできないの?

昔はできました。今でも頑張ればできるみたいです。

Scala + Playでwarファイル作成

私はTomcatとかJava由来の環境にあまり詳しくないので、わざわざwarにすることのメリットが正直よく分からないのですが、普通にPlayを起動してnginxなどと組み合わせて使う方法で事足りるような気がします。

オブジェクトのスコープはセッションごとにできないの?

できません。もしそういう事をしたければそういう機能を自分で実装するしかないです。それをサポートするための仕組みはあります。

セッションとフラッシュスコープ

Play キャッシュ API

Playで実装するときはDIの仕組みや「クライアントセッションごとに何かのオブジェクトが割り当たっている」という発想を捨てる必要があります。サーバ側に状態を持たせるとシステムをスケールさせるのが難しくなるのでやるとしても後ろにKey-Valueストアが居るようにしたいとか、肥大化しすぎとか隠ぺいしすぎとかそういう思想なのだと思います。(個人的にもWeb標準に基づいて余計なことをしないフレームワークの方が好感が持てます)

画面遷移を含むようなサイトを構築するときは上記に述べたようなセッションやキャッシュの仕組みを利用する機会があるかもしれませんが、ちゃんとRESTfulなAPIにしていればそのような場面はそれほど無い気がします。

routes編

 

デフォルト引数を与えたい(静的に引数を与えたい)

以下のような感じに書く。

GET   /some/page      controllers.Application.page(index: Int ?= 0)

参考:HTTP ルーティング

routesファイルを分けたい

以下のような感じに書く。

conf/routes:

GET /index                  controllers.Application.index()

->  /admin admin.Routes

GET     /assets/*file       controllers.Assets.at(path="/public", file)

modules/admin/conf/admin.routes:

GET /index                  controllers.admin.Application.index()

GET /assets/*file           controllers.admin.Assets.at(path="/public", file)

これで、たとえば/admin/indexが要求されるとcontrollers.admin.Application.index()が呼ばれる。

参考:サブプロジェクト

Controller編

サンプル通りやっているのに引数が足りないと怒られる

Scalaには暗黙の引数解決や型変換の仕組みが存在します。各種ドキュメントに載っているサンプル通りやっても動かない場合は、importが正しいか、implicitキーワードを省略していないかなどを確認してください。

私がよくやったミスは以下のような感じです。

Formにbindできない

def test = Action { request =>
 val reqForm = myform.bindFromRequest
 .....
}

こうすると引数が足らんと怒られます。以下のようにimplicitを追加しましょう。

def test = Action { <strong>implicit</strong> request =>
 val reqForm = myform.bindFromRequest
 .....
}

Actionではrequestにimplicitを必ず付けとく、と思えば間違いは無いです。ちなみに、暗黙の引数の解決には型がマッチしてかつimplicitで宣言された値がスコープにあるかどうかで判定しますので、名前はrequestだろうとreqだろうとrだろうとなんでも良いです。

データベースにアクセスするためのコネクションが無いと怒られる

誤:

Database.forDataSource(DB.getDataSource()) withSession { session =>
    val users = TableQuery[Users]
    users.insert((1234, "name" "test@sample.com"))
  }
 

正:

Database.forDataSource(DB.getDataSource()) withSession { implicit session =>
    val users = TableQuery[Users]
    users.insert((1234, "name" "test@sample.com"))
  }

Slickで.updateや.deleteが無いと言われる

import play.api.db.slick.Config.driver.simple._

などと書いていないでしょうか。こうすると使用しているDBに特化した暗黙の型変換が使用されなかったり、暗黙の型変換が行われず関数が見つからないなどと言われたりします。使用しているDBに特化したドライバを参照します。たとえばMySQLならば

import scala.slick.driver.MySQLDriver.simple._

です。その他のドライバはAPIマニュアルからscala.slick.driverパッケージを調べてみてください。

 

テンプレート編

テンプレート(ビュー)の書き方が良く分からない。ドキュメントにもあんまり記述がないんだけど

Playのテンプレートはシンプルであまり覚えることがありません。ドキュメントの記述も簡素になっています。以下が公式ドキュメントです。

テンプレートエンジン

かなり大雑把に言うと、@に続く文字列がコードとして解釈されるというだけです。ただ、書き方が少し特殊なので以降で個人的に迷ったところを説明します。

テンプレートでimportを先頭に書けない

そういう仕様みたいです。引数リストの後にしか書けません。引数の型名が解決できない場合は名前空間をフルネームで書くしかないみたいです。

@(arg1: Hoge, arg2: com.xxx.yyy.SomeClass)

@import models.xxx.yyy.Hoge

 

テンプレート(ビュー)で関数定義できないの?

下記のようにビューの引数リストとimportが終わった後に定義してください。importも引数リストの後に書いてください。引数で名前解決できない場合は名前空間をフルネームで書くしかないみたいです。

@(arg: String)

@import views.html.hoge

@plus(a: Int, b:Int) = @{a + b}

 

IDE編

IDEは何が良いか

一番いいのはIntelliJ IDEAです。そもそもScala+PlayのIDEと呼べるようなものはIntelliJ IDEAによるScala+PlayプラグインかEclipseによるScala IDEくらいしかなく、そしてその機能の差は歴然としているのではっきりとIntelliJが優れていると断言できます。コード補完と提案が色々な場面で効いてくれます。個人的にかなり嬉しいのは型推論で明示的に型を宣言しなかったコードに対して自動的に型宣言を追加してくれる機能です。私は面倒くさがりで型推論に頼りっぱなしなので、意図した型と違う型が返ってきたりしてコンパイルが通らず混乱することがあります。そういう面倒くさがり屋には必須と言っていい機能です。また、routesファイルやビューからメソッドの宣言にジャンプできるとかいうのも気が利いていていいと思います。

ただ、IntelliJ IDEAでPlay FrameworkをサポートできるのはUltimate版のみで、これは有償です。無償版だとプログラミング言語であるScalaは対応していますがPlayはサポートしていません。このため、たとえばPlayのビューを書くときにコード補完が効かず、不便な思いをします。

無償が良ければEclipseプラグインで提供されるScala IDE一択となると思います。これはまだ開発途上なのでビューがコンパイルできなかったり、IDEからPlayを起動してデバッガをアタッチできなかったりなどという制限があります。ちなみに、後者は致命的なように聞こえますが、私の経験上はデバッガが使えなくても意外に困るシーンはありませんでした。Scalaはオブジェクトを柔軟に文字列で表現するための機能が多数備わっているのでLoggerに吐き出して確認…という方法で事足りるケースがほとんどです。

IntelliJ IDEAは多数の言語に対応していますし、世界的に評価の高いソフトウェアなのでPlay以外にも色々使えますから、個人的には買ってもいいかなと思います(仕事ではIntelliJを使っていますがプライベートではScala IDE)。ちなみに120$くらいだったと記憶しています。

ちなみに、Playの最新版はTypesafe Activatorによって提供されていて、かつ、こいつにはブラウザで動くIDE的なエディタが付いてきます。こいつはチュートリアルを進めるのには大変優れているのですが本腰で開発をするとなると不足があります。

IntelliJ IDEAでPlayプロジェクトが作れないんだけど。Play起動できないんだけど。テンプレートを開いてもただのテキストエディタみたいな色使いになってコード補完もしてくれないんだけど

IntelliJ IDEAでPlayがサポートされるのは有償のUltimateだけです。無償のCommunity Editionではサポートしていません。ScalaプラグインにScala+Play supportと書いているのでcommunity editionでも使えるように思えてしまいますが、使えません。community editionで利用できるのはScalaのプログラミング機能だけです。Playはサポートしていません。

これ、公式ブログやStackoverflowでも同様の質問をよく見るのでどこかにデカく書いておいてほしいところです。

Scala IDEでPlayプロジェクトを作るにはどうすればいいの

以下の方法がおすすめです。

Typesafe Activatorを使って新しいPlayプロジェクトを作成します。作成したら、そのプロジェクトのディレクトリで

activator eclipse

コマンドを実行するとEclipse用の設定ファイルを吐き出してくれますので、Scala IDE(Eclipse)からそのディレクトリをプロジェクトとしてインポートできるようになります。その後、開発はScala IDEで行いつつ裏でactivator runとしておいてブラウザを開きつつコーディング、というやり方が良いように思います。

Scala IDEでどうやってPlayを実行するの?

Scala IDEからPlayを起動してアタッチする方法は現状無いみたいです。Scala IDEのRoadmapを見るとlaunching and debugging support for Play appsのサポートがend of this yearと書いてあります。この文章は2014年から変わってないので開発は遅れているようです。

Scala IDEでビュー(テンプレート)を呼び出すコードを書くとエラーになるんだけど

Scala IDEがPlayのアセットコンパイラを動かさないからです。開発中だそうです。先に述べたように、Scala IDEを使いつつ裏でplayを動かしてブラウザで確認すれば変更があるたびPlayが勝手にコンパイルをかけて.classファイルを作ってくれるので、Scala IDEがそれを読み込んでエラーが消えることもあります。逆に、Scala IDEとPlayが同時にコンパイルをかけて「ファイルにアクセスできない」とか怒られることもあります。

こういう種々の対応が面倒なので、やっぱり最初に述べたようにできればIntelliJ IDEAを買っておきたいところです。

 

その他

プロジェクトを分割したい

以下をじっくり読む。

テンプレートエンジン