株式会社 FLECT
小西俊司
 FLECTの主力言語はPlay1
◦ しかしPlay1はもはやメンテされていない模様
◦ 1.3が出ると一瞬聞いたような気もするが。。。(--
 Play1開発者がPlay2を使う場合に
◦ Play1でのやり方との対比でPlay2での方...
 自力で頑張れ
◦ List, Map, Option, パターンマッチ, case classあたりがわかれ
ばとりあえず使える
 禁止技
◦ 途中でReturn
◦ Nullとの比較(Optionを使用する)
 Scalaでどう書けば...
 Play2のインストールはzipを展開してPATHを通
すだけ
 しかし「play(.bat)」という実行スクリプトの名
前がPlay1とバッティングする。。。
◦ →実行スクリプトを「play2(.bat)」とリネームすれば問
題なく共...
package controllers
import play.api.mvc.Controller
import play.api.mvc.Action
object Application extends Controller {
def ...
 多くのメソッドでリクエスト情報を暗黙引数として要求される
ので常にimplicitキーワード付きで参照するようにする
package controllers
import play.api.mvc.Controller
import pla...
@(name: String)(implicit lang: Lang)
@main(Messages("hello")) {
<div>
@Messages("hello") @name
</div>
}
これがないとMessageの国際化が...
 ワイルドカードは使えない
 Actionの参照には引数を含める必要がある
 PATHの一部をActionの引数として渡せる
 GETパラメータはActionの引数として渡せるがPOSTパラメータは渡せ
ない
 パラメータには省略時の...
 Ok、Redirect、BadRequest、などHttpステータ
ス毎にメソッドが用意されている
 Content-Typeは引数によって自動的に識別
◦ Play1の「renderXXX」に相当するメソッドはない
◦ あるいは
のよう...
 Formを使用する
◦ Case classを作ってそこにマッピングするのが楽
private val queryForm = Form(mapping(
"id" -> optional(text),
"kind" -> of[Query...
 個別に取得したい場合は以下のようにすると取れ
る
◦ ユーティリティメソッドを作っておかないとものすごく
面倒
val name: Option[String] =
request.body.asFormUrlEncoded.flatMap...
 Play1とほとんど変わらない
◦ application.confの「application.langs」で使用する言
語を宣言
◦ 言語毎に「messages.xx」をconfディレクトリに作成
◦ リソースの参照はMessagesクラ...
 Play1の「conf/dependencies.yml」相当のファ
イルは「project/Build.scala」
 JDBCドライバはデフォルトで入っていないので
PostgreSQLなどのJDBCドライバを使用する場合
は自分で組...
 Application.confにデータベース定義を追加
 Play1ではDBはひとつしか定義できなかったが複数定
義可能になった
 ORMapperはない
 代わりにAnormが組み込まれている
◦ 多分SelectBuilderと...
 CacheAPIはset,get,removeの3メソッドのみと
なった
 Memcachedを使用する場合はプラグインが必要
◦ https://github.com/mumoshu/play2-memcached
◦ Play1と同じ...
 https://github.com/orefalo/play2-
authenticitytoken
 をいれると、Play1相当のことができるらしい
◦ MemcachedもそうだがPlay1にあった機能が足りてない
場合は有志がプラ...
 JSONライブラリがgsonからjackson(のScalaラッ
パーであるjerkson)に変わった
 Case classをJSONにparse/formatするだけなら
JSON.formatをimplicitで定義するだけで良い。...
 ScalaのXMLリテラルがそのまま使用できる
◦ もっとも最近ではREST APIはほとんどJSONなのでほと
んど使うことはない
◦ 以前にXMLリテラルでXMLを組み立てようとした際に、
手続き的な処理が必要になってどうするのが良いの...
 JavaScriptやCSSは「app/assets」に置く
 このフォルダに置いたファイルでは
◦ CoffeeScriptがJavaScriptに変換される
◦ LESSがcssに変換される
◦ js/cssのminify版が生成され...
 Play1と同じく実体はCookie
 セッションIDがない
◦ Memcacheを使用する場合に困る
◦ 自前のFilterで対応
//OAuth認証のTokenをsessionに持たせる
Ok(views.html.login(url...
 Play1と同じく次のリクエストまで有効なFlashが使用でき
る
◦ 仕組みもPlay1と同じ
◦ https://github.com/shunjikonishi/sqltool/blob/master/app
/controllers...
 Request.body.asMultipartFormDataからアップ
ロードされたファイルをjava.io.Fileとして取り出
せる
◦ Play1は引数にFileを宣言するだけだったのですごく面倒
になった
◦ https://g...
 Ok.sendFileメソッドを使う
◦ Play1では添付ファイルのダウンロードと削除を同時に
やろうとするとすごく面倒だったのが楽になった
◦ https://github.com/shunjikonishi/sqltool/blob/...
 Play1と同じようにWSクラスで外部WebServiceに
アクセス可能
 Play1とほぼ同じような使い方だが同期APIがなく
なって非同期APIのみなので同期で使用したい場
合は自分でFutureから結果を取り出す必要がある
 Play1のPluginのように全リクエストで共通的に
行う処理はFilterクラスとして実装
◦ SessionIDFilter(リクエストにセッションIDを付加する)
◦ https://github.com/shunjikonishi...
 Globalクラスの宣言にWithFiltersで組み込み
 https://github.com/shunjikonishi/sqltool/blo
b/master/app/Global.scala
object Global ext...
 Play1の@throwsや@beforeのような処理を行う場合
はActionの合成を使用する
◦ https://github.com/shunjikonishi/sqltool/blob/master/
app/controllers...
 Akkaを直接使う
◦ https://github.com/shunjikonishi/sqlsync/blob/ma
ster/app/Global.scala
//HerokuのWebDynoを眠らせないために自分自身をポーリング
v...
 ログライブラリはlog4jからLogback+SLF4Jに変
更になった
 play.api.Loggerを直接使うこともできるが、個
別にLoggerインスタンスを生成して使用すること
もできるようになった
 ログレベルの設定はapp...
 Play1にあったAppIDはない
 ので、開発/本番環境で設定を切り替えたい場合
はapplication.conf自体を切り替える
◦ 起動コマンド
play run –Dconfig.resource=prod.conf
 Con...
 HerokuのスケジューラからPlay2を起動したい場合
◦ 重いので可能であればJavaクラス単体にするなどした方が良
い
 target/startスクリプトに起動オプションを渡して起
動
target/start –Dsqltool...
 Global.onStartで起動オプションによって処理を分岐
◦ 終了時には問答無用でSystem.exitしている(多分問題ないは
ず)
◦ https://github.com/shunjikonishi/sqltool/blo
b/...
 Play1でできていたことは頑張れば全部できる
 Play1では標準機能でできていたことがプラグイ
ンが必要になって面倒になった気がするが、言い
換えれば組み合わせの自由度が上がったというこ
とでもある
◦ もっとも発展途上の印象もぬぐえ...
Upcoming SlideShare
Loading in …5
×

Play1 to Play2

3,308 views
3,181 views

Published on

Published in: Technology
0 Comments
8 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
3,308
On SlideShare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
11
Comments
0
Likes
8
Embeds 0
No embeds

No notes for slide

Play1 to Play2

  1. 1. 株式会社 FLECT 小西俊司
  2. 2.  FLECTの主力言語はPlay1 ◦ しかしPlay1はもはやメンテされていない模様 ◦ 1.3が出ると一瞬聞いたような気もするが。。。(--  Play1開発者がPlay2を使う場合に ◦ Play1でのやり方との対比でPlay2での方法を知る ◦ 言語とFrameworkの両方を同時に学ぶのは辛いのでとりあえ ずScalaが良く分かってなくてもなんとなく開発できる気にす るのがゴール ◦ これどうするんだっけ?と思った時に見る資料(主に俺が)  ただしBest Practiceはまだまだ模索中なのでもっと良 い方法があるはずとも思う。 ◦ というかPlay2自体まだまだそんな感じ ◦ 間違ってもここに書いてあることを鵜呑みにしてはいけない
  3. 3.  自力で頑張れ ◦ List, Map, Option, パターンマッチ, case classあたりがわかれ ばとりあえず使える  禁止技 ◦ 途中でReturn ◦ Nullとの比較(Optionを使用する)  Scalaでどう書けば良いのかわからん!と思った時にはと りあえずJavaだと思って書けばOK  慣れるまでimportでワイルドカードは使わない ◦ Play(というかScala全般)でimportはワイルドカードで書く文化 でサンプル等もほとんどそうなっているが、それだとクラス構成 が全く分からない。 ◦ 特にimplicit関数を把握しないまま使うのは危険だと思う ◦ 列挙型やImplicit関数などワイルドカードでインポートしないと 使いづらいものもあるが、それを見極めるためにも最初は自力で 書く
  4. 4.  Play2のインストールはzipを展開してPATHを通 すだけ  しかし「play(.bat)」という実行スクリプトの名 前がPlay1とバッティングする。。。 ◦ →実行スクリプトを「play2(.bat)」とリネームすれば問 題なく共存して使える
  5. 5. package controllers import play.api.mvc.Controller import play.api.mvc.Action object Application extends Controller { def index = Action { Ok(views.html.index("Your new application is ready.")) } def hello = Action { Ok("Hello world") } } デフォルトで定義されている Action Okは「200 OK」のレスポンス views.xxxxはviewsフォルダにあ るテンプレートファイルへの参照 で renderTemplate相当 renderText相当  Play1と違ってワイルドカードルーティングはないのでroutesの 編集も必要
  6. 6.  多くのメソッドでリクエスト情報を暗黙引数として要求される ので常にimplicitキーワード付きで参照するようにする package controllers import play.api.mvc.Controller import play.api.mvc.Action object Application extends Controller { def index = Action { implicit request => Ok(views.html.index("Your new application is ready.")) } def hello = Action {implicit request => Ok("Hello world") } } Implicitをつけると暗黙の引数として利用でき る
  7. 7. @(name: String)(implicit lang: Lang) @main(Messages("hello")) { <div> @Messages("hello") @name </div> } これがないとMessageの国際化がされない  使用するパラメータはすべて宣言する必要がある ◦ 最初はPlay1に比べてかなり面倒な気がするがパラメータの不 備がコンパイルエラーで拾えるのがメリット  マジックワードは「@」のみ  「@{…}」で自由にScalaコードを記述可能  上の例で使用している機能 ◦ 親テンプレートとして@mainを呼び出している ◦ @Messagesで国際化されたメッセージリソースを出力 ◦ 引数@nameを出力
  8. 8.  ワイルドカードは使えない  Actionの参照には引数を含める必要がある  PATHの一部をActionの引数として渡せる  GETパラメータはActionの引数として渡せるがPOSTパラメータは渡せ ない  パラメータには省略時のデフォルトが定義できる # Routes # This file defines all application routes (Higher priority routes first) # ~~~~ # Home page GET / controllers.Application.index GET /hello controllers.Application.hello GET /hello2/:name controllers.Application.hello2(name) GET /hello2 controllers.Application.hello2(name ?= "guest") # Map static resources from the /public folder to the /assets URL path GET /assets/*file controllers.Assets.at(path="/public", file) Play1に比べて面倒
  9. 9.  Ok、Redirect、BadRequest、などHttpステータ ス毎にメソッドが用意されている  Content-Typeは引数によって自動的に識別 ◦ Play1の「renderXXX」に相当するメソッドはない ◦ あるいは のように「as」メソッドで明示  Play1のようなトリックはない ◦ renderXXXXが実はExceptionだとか ◦ Controllerのメソッドを呼ぶと勝手にリダイレクトされ るとか Ok(“””{“name”:”konishi”}”””).as(“application/json”)
  10. 10.  Formを使用する ◦ Case classを作ってそこにマッピングするのが楽 private val queryForm = Form(mapping( "id" -> optional(text), "kind" -> of[QueryKind], "name" -> text, "group" -> default(text, ""), "sql" -> text, "desc" -> optional(text), "setting" -> optional(text) )(QueryInfo.apply)(QueryInfo.unapply));  https://github.com/shunjikonishi/sqltool/blob /master/app/controllers/QueryTool.scala
  11. 11.  個別に取得したい場合は以下のようにすると取れ る ◦ ユーティリティメソッドを作っておかないとものすごく 面倒 val name: Option[String] = request.body.asFormUrlEncoded.flatMap(_.get("name").map(_.head));
  12. 12.  Play1とほとんど変わらない ◦ application.confの「application.langs」で使用する言 語を宣言 ◦ 言語毎に「messages.xx」をconfディレクトリに作成 ◦ リソースの参照はMessagesクラスを使用  同一ファイル内に複数言語のメッセージを定義し たい場合はGlobal.scalaにPlay1で使用していた ResourceGenを組み込むことができる ◦ https://github.com/shunjikonishi/sqltool/blob/ma ster/app/Global.scala
  13. 13.  Play1の「conf/dependencies.yml」相当のファ イルは「project/Build.scala」  JDBCドライバはデフォルトで入っていないので PostgreSQLなどのJDBCドライバを使用する場合 は自分で組み込む必要がある  https://github.com/shunjikonishi/sqltool/blo b/master/project/Build.scala  Herokuで使う場合、Play1ではivysetting.xmlが 自前で用意できなかったためリポジトリの追加が できなかったがPlay2では問題なくできる ◦ flectCommonなどの内部jarをリポジトリから取得でき る
  14. 14.  Application.confにデータベース定義を追加  Play1ではDBはひとつしか定義できなかったが複数定 義可能になった  ORMapperはない  代わりにAnormが組み込まれている ◦ 多分SelectBuilderとの相性は良い ◦ https://github.com/shunjikonishi/sqltool/blob/master/ app/models/QueryManager.scala //グループ名の一部を抜き出す処理 SQL(""" SELECT distinct groupname FROM sqltool_sql WHERE groupname LIKE {gl} ORDER BY groupname """ ).on( "gl" -> (parent + "/%") ).apply.map{ row => val g = row[String]("groupname").substring(parent.length + 1); g.split("/")(0); }.toSet.toList;
  15. 15.  CacheAPIはset,get,removeの3メソッドのみと なった  Memcachedを使用する場合はプラグインが必要 ◦ https://github.com/mumoshu/play2-memcached ◦ Play1と同じくserializableなオブジェクトをキャッシュ に保存可能 ◦ Case classはserializableなのでそのままキャッシュに 保存可能
  16. 16.  https://github.com/orefalo/play2- authenticitytoken  をいれると、Play1相当のことができるらしい ◦ MemcachedもそうだがPlay1にあった機能が足りてない 場合は有志がプラグインとして作っている印象  Play1の時に作った自前のCSRFモジュールをプラ グインとして移植しても良いけど微妙。
  17. 17.  JSONライブラリがgsonからjackson(のScalaラッ パーであるjerkson)に変わった  Case classをJSONにparse/formatするだけなら JSON.formatをimplicitで定義するだけで良い。  加工が必要な場合は自分でFormatを定義 ◦ JSONのキーを変数名とは別にしたい ◦ Case class中のあるフィールドはJSONに含めたくない  https://github.com/shunjikonishi/sqltool/blo b/master/app/models/QueryInfo.scala  https://github.com/shunjikonishi/sqltool/blo b/master/app/models/SQLToolImplicits.scala
  18. 18.  ScalaのXMLリテラルがそのまま使用できる ◦ もっとも最近ではREST APIはほとんどJSONなのでほと んど使うことはない ◦ 以前にXMLリテラルでXMLを組み立てようとした際に、 手続き的な処理が必要になってどうするのが良いのか 迷った記憶があるが詳細は忘れた。
  19. 19.  JavaScriptやCSSは「app/assets」に置く  このフォルダに置いたファイルでは ◦ CoffeeScriptがJavaScriptに変換される ◦ LESSがcssに変換される ◦ js/cssのminify版が生成される(xxx.min.jsでアクセスで きる)  イメージなどは「public」に置く。 ◦ assetsに置いても良いと思うが意味がないし、多分こっ ちの方が速い
  20. 20.  Play1と同じく実体はCookie  セッションIDがない ◦ Memcacheを使用する場合に困る ◦ 自前のFilterで対応 //OAuth認証のTokenをsessionに持たせる Ok(views.html.login(url)).withSession( "token" -> token.getOAuthToken() )
  21. 21.  Play1と同じく次のリクエストまで有効なFlashが使用でき る ◦ 仕組みもPlay1と同じ ◦ https://github.com/shunjikonishi/sqltool/blob/master/app /controllers/QueryTool.scala//ファイル インポート後にインポート件数を付けてメイン画面にリダイレクト Redirect("/main").flashing( "Import-Insert" -> insertCount.toString, "Import-Update" -> updateCount.toString );  取得側のコード ◦ Implicit requestが必要 ◦ https://github.com/shunjikonishi/sqltool/blob/master/app /controllers/Application.scala def main = Action { implicit request => val importInsert = flash.get("Import-Insert").getOrElse("0").toInt; val importUpdate = flash.get("Import-Update").getOrElse("0").toInt; … }
  22. 22.  Request.body.asMultipartFormDataからアップ ロードされたファイルをjava.io.Fileとして取り出 せる ◦ Play1は引数にFileを宣言するだけだったのですごく面倒 になった ◦ https://github.com/shunjikonishi/sqltool/blob/ma ster/app/controllers/QueryTool.scaladef importSql = Action { implicit request => request.body.asMultipartFormData match { case Some(mdf) => mdf.file("file") match { case Some(file) => ... case None => BadRequest; } case None => BadRequest; } } なんか冗長なのでもっときれいな書き方 があると思う
  23. 23.  Ok.sendFileメソッドを使う ◦ Play1では添付ファイルのダウンロードと削除を同時に やろうとするとすごく面倒だったのが楽になった ◦ https://github.com/shunjikonishi/sqltool/blob/ma ster/app/controllers/QueryTool.scala def exportSql = Action { implicit request => val file = File.createTempFile("temp", ".sql"); try { man.exportTo(file); Ok.sendFile(file, fileName={ f=> "export.sql"}, onClose={ () => file.delete()}); } catch { case e: Exception => e.printStackTrace; Ok(e.toString); } }
  24. 24.  Play1と同じようにWSクラスで外部WebServiceに アクセス可能  Play1とほぼ同じような使い方だが同期APIがなく なって非同期APIのみなので同期で使用したい場 合は自分でFutureから結果を取り出す必要がある
  25. 25.  Play1のPluginのように全リクエストで共通的に 行う処理はFilterクラスとして実装 ◦ SessionIDFilter(リクエストにセッションIDを付加する) ◦ https://github.com/shunjikonishi/sqltool/blob/ma ster/app/jp/co/flect/play2/filters/SessionIdFilter.sc ala ◦ AccessControlFilter(Basic認証とIP制限) ◦ https://github.com/shunjikonishi/sqltool/blob/ma ster/app/jp/co/flect/play2/filters/AccessControlFilt er.scala ◦ 便宜的にSQLTool内で作成しているが必要に応じて Plugin化する
  26. 26.  Globalクラスの宣言にWithFiltersで組み込み  https://github.com/shunjikonishi/sqltool/blo b/master/app/Global.scala object Global extends WithFilters(SessionIdFilter, AccessControlFilter) { … }
  27. 27.  Play1の@throwsや@beforeのような処理を行う場合 はActionの合成を使用する ◦ https://github.com/shunjikonishi/sqltool/blob/master/ app/controllers/GoogleTool.scala //GoogleSpreadsheetの設定が有効でない場合はInternalServerErrorを返す def filterAction(f: Request[AnyContent] => Result): Action[AnyContent] = Action { request => if (GoogleSpreadsheetManager.enabled) { f(request); } else { InternalServerError("Google account is not setuped."); } } def showSheet(bookName: String, sheetName: String) = filterAction { implicit request => ... }
  28. 28.  Akkaを直接使う ◦ https://github.com/shunjikonishi/sqlsync/blob/ma ster/app/Global.scala //HerokuのWebDynoを眠らせないために自分自身をポーリング val appname = sys.env.get("HEROKU_APPLICATION_NAME"); if (appname.nonEmpty) { Akka.system.scheduler.schedule(0 seconds, 10 minutes) { WS.url("http://" + appname.get + ".herokuapp.com/assets/ping.txt").get() } }
  29. 29.  ログライブラリはlog4jからLogback+SLF4Jに変 更になった  play.api.Loggerを直接使うこともできるが、個 別にLoggerインスタンスを生成して使用すること もできるようになった  ログレベルの設定はapplication.conf内で個別に 行える logger.root=ERROR logger.play=INFO logger.application=DEBUG logger.jp.co.flect=INFO #logger.com.google=DEBUG logger.Schedule=INFO
  30. 30.  Play1にあったAppIDはない  ので、開発/本番環境で設定を切り替えたい場合 はapplication.conf自体を切り替える ◦ 起動コマンド play run –Dconfig.resource=prod.conf  Confファイル内では別のconfファイルをインク ルードでき、キーをオーバーライドすることが可 能include "application.conf" key.to.override=blah
  31. 31.  HerokuのスケジューラからPlay2を起動したい場合 ◦ 重いので可能であればJavaクラス単体にするなどした方が良 い  target/startスクリプトに起動オプションを渡して起 動 target/start –Dsqltool.mode=schedule  ローカルで動かす場合はあらかじめ「play stage」コ マンドを叩いてlibをビルドする必要がある  Windowsの場合は.batは生成されないので自分でバッ チファイルを書く必要がある  コマンドが長いのでrakeで起動するようにしている https://github.com/shunjikonishi/sqltool/blob/master/sqltool.bat https://github.com/shunjikonishi/sqltool/blob/master/Rakefile
  32. 32.  Global.onStartで起動オプションによって処理を分岐 ◦ 終了時には問答無用でSystem.exitしている(多分問題ないは ず) ◦ https://github.com/shunjikonishi/sqltool/blo b/master/app/Global.scala val mode = sys.props.get("sqltool.mode").getOrElse("web"); mode match { case "schedule" => Schedule.main(); System.exit(0); case “setup” => … System.exit(0); case _ => }
  33. 33.  Play1でできていたことは頑張れば全部できる  Play1では標準機能でできていたことがプラグイ ンが必要になって面倒になった気がするが、言い 換えれば組み合わせの自由度が上がったというこ とでもある ◦ もっとも発展途上の印象もぬぐえない  コード量はおそらくPlay1の半分以下になる ◦ case classとListがあるだけでもJavaの生産性をはるか に凌駕していると思う 多分使える

×