1周遅れのscala入学 #nds41
TRANSCRIPT
第41回長岡IT技術者勉強会
1週周遅れのScala入学
2015/4/11 @nemuzuka
自己紹介• 片桐 一宗(かたぎり かずむね)
• id:nemuzuka
• @nemuzuka
• サーバサイドにJavaを使用したWebアプリケーションの開発を主にしております
• フリーランス(vss.jp.net)
Scala再入学のきっかけ
システムを新しく構築するチャンス• AWSのサービス使用
• S3 / CloudSearch / CloudFront…
• 全ての操作で画面切り替えとかちょっと…
• AjaxでDOM書き換え
• HTML5
• IE?最新しかサポートしない!
• スケジュールがタイト
• 要望がコロコロ変わりそう…
そこで選んだのが
• サーバサイドはJava
• Tomcat
• Seasar2
• SAStruts
• S2Dao
+今まで培ってきた ・diconファイル ・薄いラッパー
ちょっと 開発のパワーが足りないから 外部の人の協力を得よう
『新規なのにJavaなんですか』
『新規なのにSeasar2 なんですか( ´_ゝ`)』
えもいわれぬ老害感
結局Javaの構成で 無事カットオーバー できましたが...
何か新しいこと やらないといけないかな
選択肢は2つ• 別のフレームワークを学ぶ
• JavaEE
• Spring
• Play Framework
• 新しい言語を学ぶ
• 静的型付けが良い
• IDEが使えた方が良い
あっ!
入学してた。
当時は「できない子」
• bot写経で躓き、「ガッ」できなかった
• みんなの「XXXのコード見せてください」の画面切り替えが早すぎて追いつけなかった
• 単純に手が遅い
再入学の後押し• Javaの資産が使える
• Apache Commonsとか
• AWSのSDKだって使える
• 関数型プログラミングもオブジェクト指向プログラミングもサポート
• とりあえず「使う」だけなら関数脳にならなくても良い
• 型推論でスクリプト言語のようにも見える
• でも、IDEで追いやすい
「Javaやってます」 よりも
「Scalaやってます」 の方がなんとなく仕事が来そう
というわけで Scala再入学することに
どうやって再入学したか?
• 今更、NullPoGaBot作ってもなー
• どうせやるなら何か案件が始まった時に使えるものにしたい
S2でやってたことを Scalaでやるならどうするか に置き換えてみよう
フレームワークの置き換え
機能 JAVA SCALA
WEBフレームワーク SAStrutsScalatra
+scalata-forms
テンプレートエンジン JSP Scalate(SSP)
ORM S2Dao Slick
こんなときどうする(1)
~トランザクション管理~
トランザクション• DBの変更を
• 適用する(commit)
• 取り消す(rollback)
• これをそれぞれのプログラムで行うと
• DBに対する操作とビジネスロジックが混在する
• ソースコードの見通しも良くない
• コネクションの解放漏れ等にも繋がる
S2だと DI• S2の仕組みに乗れば自動的にトランザクションが効く
• @Bindingを付与して実装クラスをInjection
• 正常終了(何も例外が発生しない)時commit
• 例外が発生すればrollback
• DI使うのに外部XMLは不要
• コネクションプールもdiconに書くだけで使える
Scala(Scalatra)だと filter• Scalatraのinitでコネクションプール設定
• filterでトランザクション制御
• トランザクション開始
• 処理呼び出し
• 正常終了(何も例外が発生しない)時commit
• 例外が発生すればrollback
ソースはこんな感じ
//Filter class class TransactionFilter(db: Database) extends Filter {
def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = {
db withDynTransaction { // 1. トランザクションを開始し、ThreadLocalにSessionを格納 chain.doFilter(req, res) // 2. Servletの処理を行う } // 3. 正常時commit、例外発生時rollback } }
DBDatabase.dynamicSession
をimportThreadLocal取得して処理を行うことが可能
class ScalatraBootstrap extends LifeCycle {
val cpds = new ComboPooledDataSource() // 1.設定ファイルを元にコネクションプールを生成
override def init(context: ServletContext) { // 2.Scalatra起動時に一回だけ呼ばれるメソッド
val db = Database.forDataSource(cpds) // 3. Slick用データソースの取得 context.addFilter("transactionFilter", new TransactionFilter(db)) // 4. TransactionFilterをfilterとして登録 context.getFilterRegistration(“transactionFilter").addMappingForUrlPatterns(
util.EnumSet.allOf(classOf[DispatcherType]), true, “/*")
// 5. Servletの定義が続く… } }
これでこんなLayer設計ができます
・Servlet ・Service →DBアクセスしないので、Sessionをコード上で意識しない
・Dao →ThreadLocalからSessionを取得してSQL発行
こんなときどうする(2)
~認証・認可チェック~
認証・認可• 認証
• リクエストを送ってきた人が正規のユーザであることを確認すること
• ID / パスワードでログインしている
• 本人しかしらない筈なので、正規のユーザとみなす
• 認可
• その機能を利用する権限の有無を確認すること
• 管理者権限を所有していないのに、管理者機能を使用できるのはNG
• Webアプリの場合、URLを直接叩かれる可能性があるので、特に注意する必要あり
S2だと S2AOP• ログイン成功時、HttpSessionにログイン情報を設定
• 受けたリクエストが認証済みでなければならない場合
• Interceptorを使用して、Actionの呼び出し前にログイン情報がHttpSessionに存在するかチェック
• 存在しなければログイン画面へリダイレクト
• 存在するが、そのActionを使用する権限をユーザが持っていない場合、不正アクセスが来たとみなし、しかるべき画面にリダイレクト
• AOPの定義はdiconファイルに定義
• パッケージ、クラス名等の正規表現で定義できる
Scala(Scalatra)だと filter• ログイン成功時、HttpSessionにログイン情報を設定
• リクエストURIを元に受けたリクエストが認証済みで無ければならない場合
• filterでログイン情報がHttpSessionに存在するかチェック
• 存在しなければログイン画面へリダイレクト
• 存在するが、そのServletを使用する権限をユーザが持っていない場合、不正アクセスが来たとみなし、しかるべき画面にリダイレクト(これは、Servlet側機能)
こんなときどうする(3)
~コード自動生成~
ER図を常に信じられる状態にしたい
3.ソースコード (絶対に手動で修正しない)
2.RDBMS
1.ER図
• S2Dao-CodeGen
• Slick code generator
S2だと
Scala(Slick)だと
こんなときどうする(4)
~動的SQL発行~
画面に入力された項目だけwhere句に追加
未入力の場合、その項目はwhere句に含めない
S2だと IFコメント• 条件に応じてSQLを変更することが可能
• /* IF 条件 */…/*END*/
• 条件がtrueの場合、/*IF*/と/*END*/に囲まれた部分が評価される
/*IF hoge != null*/hoge = /*hoge*/‘abc’/*END*/
引数hogeがnullでない場合にのみ、 hoge = hogeの値 がSQL文に追加される
Scala(Slick)だと 生Preparedstatement
• ゴニョゴニョ頑張れば他のやり方でできる気もするけど、こっちの方が確実だと思う
• where句のカラムが動的に変わらないのであれば、StaticQueryを使用した方が良い
• 複雑なテーブル結合する場合はStaticQueryやPreparedstatement使ったほうが余計なことにハマらなくて良いかも
• S2Dao使ってる時でも生SQLを発行してた
• ORMは楽になるところだけ使う
def coffeeByName(name: String) = sql"select * from coffees where name = $name".as[Coffee] println("Coffee Colombian: " + coffeeByName("Colombian").firstOption)
まだいろいろあるけど、 ドキュメント読めば何とかなります
Java使いがScalaと戯れた感想
型推論
• 変数名定義の時にクラス名の有無でこんなに違うかーってくらいスッキリします
[Scala] val credentials = new BasicAWSCredentials(accessKey, secretKey) val s3client = new AmazonS3Client(credentials) val localFile = new File("ローカルファイルパス") val bucketName = "バケット名" val filePath = "S3のアップロード先のパス" val upReq = new PutObjectRequest(bucketName, filePath, localFile) s3client.putObject(upReq)
[Java] AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey); AmazonS3 s3client = new AmazonS3Client(credentials); File localFile = new File("ローカルファイルパス"); String bucketName = "バケット名"; String filePath = "S3のアップロード先のパス"; PutObjectRequest upReq = new PutObjectRequest(bucketName, filePath, localFile); s3client.putObject(upReq);
名前付き引数• 大好きです。Javaにもあれば良いのに
• 使う側が意識して設定するようになるのがイイと思います
• 引数の順番が変わった時にも追従してくれます
• 特にSlickのデータモデル(case class)のインスタンスを生成するときは嬉しい
• カラムの追加や順番を変更することが多いので
[Scala]
case class User(age:Int, name:String) ・ ・ ・
val hanako = User(7, “はなこ”) // ① val jiro = User(age = 38, name = “二郎”) // ② val taro = User(name = “太郎”, age = 17) // ③
→全てUserクラスのインスタンスが生成可能だが、 case class User(name:String, age:Int)
と変更した場合、①はコンパイルエラーになる
index付きループ• Javaにもあれば良いのに
• Javaだと拡張forを諦めてfor(int i = 0; i < list.size(); i++)
[Scala]
val list = List("A", "B", "C") for((e, index) <- list.zipWithIndex) { // eには該当要素、indexには該当indexが格納される
・ ・ ・
}
まだまだあるよ• if とか for は式なので値を返せる
• 「このifで何をしたいんだっけ?」が見えるようになるのがイイ!
• 複数の戻り値を返せる(タプル)
• わざわざ戻り値用のclass作らなくてもいいんです
• traitでmix-inがすごい
• チェック例外がない
• 個人的にはあってもいいと思うけど…(設計思想変える必要あり)
• Either(さっきのヤツ!)
• 比較は「==」でOK
• equalsを使わなくて怒られる新人減ります
• breakが変
• もなど / かりー → よくわかりません
再入門に準備したもの
まとめ
• 結構面白いです、Scala
• 潤沢なメモリ+SSD必須
• 金食い虫なので仕事に使うなら上の理解が必要かも
• 次はテスト周りをしっかりと