Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
株式会社 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,551 views

Published on

Published in: Technology
  • Be the first to comment

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の生産性をはるか に凌駕していると思う 多分使える

×