めんどくさくない Scala #kwkni_scala

Preview:

DESCRIPTION

怖くない Scala 勉強会での発表資料です。 http://connpass.com/event/3420/

Citation preview

まず、あなたと Scala の出会いを思い出してみてください。

3

引用しますね...

Team Geek ―Googleのギークたちはいかにしてチームを作るのか

[単行本(ソフトカバー)]

Brian W. Fitzpatrick (著), Ben Collins-

Sussman (著), 角 征典 (翻訳)

出版社: オライリージャパン (2013/7/20)

ISBN-10: 4873116309ISBN-13: 978–4873116303発売日: 2013/7/20

4

“初心者にとってソフトウェアとはどのようなものだろうか?”

“ソフトウェアはユーザーのことを歓迎しているだろうか?”

Team Geek 「6.1.1 第一印象に注目する」より引用

5

“使ってもらいやすくなっているだろうか?”

“熟練者にとって使いやすそうで実用的なものになっているだろうか?”

Team Geek 「6.1.1 第一印象に注目する」より引用

6

“すぐに生産性の向上をアピールできるだろうか?”

“それとも学習曲線が急で何度も涙が出るようなものだろうか?”

Team Geek 「6.1.1 第一印象に注目する」より引用

7

“もっと具体的に言うと、最初の 30 秒間のユーザーエクスペリエンス

はどのようなものだろうか?”

“ここでは感情的な答えが聞きたい。1 分後にどのように感じるだろうか?”

Team Geek 「6.1.1 第一印象に注目する」より引用

8

ここで質問。

9

初めて Scala でコードを書いてみたときどうだったか覚えていますか?

とりあえず Hello World を print した後次に何をやったっけ?何を調べたっけ?

10

フレームワーク、ライブラリ、ツール・・どれもすんなりと扱えて、すごく便利で「これは捗るwww」と思いましたか?それとも Try & Error で 頑張りましたか?

11

自分がまだ不勉強なせいなのかそれとも ”それ” が不親切だからなのか

初心者には区別がつかない。

入口は、極力ハマりポイントをなくしてすぐに試せる親切な手順が書いてあって“ゆとり仕様” くらいでちょうどいい。

12

そこを補完する Scala 逆引きレシピはすばらしいですね!今すぐ買いましょう!!

Scala逆引きレシピ (PROGRAMMER’S RECiPE)

[単行本(ソフトカバー)]

竹添 直樹 (著), 島本 多可子 (著)

出版社: 翔泳社 (2012/7/3)

言語 日本語ISBN-10: 4798125415ISBN-13: 978–4798125411発売日: 2012/7/3

13

で、ここからが本題。

14

“「Scala は捗る!」を根付かせたい”

Scala は書いていて楽しいし、一番好きな言語だけどいくつか面倒なことや残念なことがあります。

そのめんどくさいことを減らすための提案とそれを意識して私がつくっているものを

少し紹介したいと思います。

15

一般論としてのめんどくささ

16

めんどくさいのはダメ・めんどくさいものを頑張って使うのは有害である・本題に集中できず、他のミスを誘発しやすい・奥が深い症候群に陥りやすい・今まで投資した時間を正当化するために固執しがち・たいがいそのノウハウのポータビリティは低い・そもそも楽しくない(人生は短い)

 結果、確実に生産性が低下する(自明)17

Scala でめんどくさいこと?

18

すぐに思いつくこと...

・コンパイルが遅すぎる・Scala、sbt バージョン非互換・言語仕様の学習が大変そう・スタイルが統一されなさそう・そもそも JVM な時点でちょっと(ry

19

Compile! Compile! Compile!

20

コンパイルが遅すぎる

・良いマシンを...(震え声)・sbt の ~;compile とか ~;test でごまかす・コンパイルが遅くなる言語機能を避ける...

・何でもかんでもコンパイルしない(個人の意見です)

「何でもかんでもコンパイルしない」について Skinny

Framework のデモをして説明します

21

デモ:Yeoman って知ってますか?Node.js で動く Rails generator 的なもの。

Windows でも Node と Ruby 入れれば使えるはず。

brew install nodejs npm install -g yo npm install -g generator-skinny mkdir hello-skinny cd hello-skinny yo skinny ./skinny run

22

もう重たくない Scala 開発・Controller、Model は Jetty 起動なしでテスト可能・DB 連携は実行せずに正しさを確認できないので、お決まりな CRUD は(ある程度)動的でもよい・View の調整が圧倒的に多いので、いちいちコンパイルせず実行時エラーで確認(& Controller のテストを CI)・CoffeeScript なども開発 mode は実行時変換(予定)・production だけ Scalate precompile も可能・サンプルはこちらにあります

23

24

Scala、sbt の非互換・今使っているものは足かせにならないか?・ライブラリ、sbt プラグインは厳選した方がよい・ワーストケースで移行が容易な実装・テストにしておく・ ほとんどいじらない共通部分はあえて Java で書いてメンテコストを抑える(Scala 側でラップは必要)・フリーライダーで乗り切るのは多分無理(GitHub 時代の言語、自ら貢献する姿勢で)

25

26

言語仕様の学習が大変そう・最低限必要なレベルまではそれほど大変ではないはず・コップ本の前にもっとライトな本を流してもよい・”Scala for the impatient” あたりがオススメ・scalatutorials.com で手を動かすとか・困ったら scalajp の ML で質問(怖くない)

27

TMTOWTDI?“There’s more than one way to do it”

28

スタイルが統一されなさそう・書き方は基本的に Scala Style Guide に従う・scalariform は問答無用で適用する・汎用コーディング規約は存在しないので、チームの中での共通認識をすりあわせていく・理解度の一番低い人に合わせすぎない・何かに感染している人を周りは生暖かく見守る・歩み寄る 、ストレスを溜めない

29

30

そもそも JVM な時点で(ry

・LL だったらもっと簡単なのに... と思われたら負け・sbt 初回起動... 巨大な依存ライブラリは極力減らす・cs や g8 は好きだけど sbt だけで済まないのは微妙...

・ライブラリを作るなら REPL で簡単に試せることは重視する(import はなるべく少なく)・今日から Scala を始めたとしてどう思うか?

31

あなたの “Scala ではこれが普通” はただ慣れてしまっただけかもしれない...

一度、見直してみると...

32

ここからは宣伝。

33

RDB とのやり取り

34

 No More SQL? Really?

・「SQL ならもう分かってるけど、このライブラリでどう書けばいいか分からん... ドキュメントにないな... サンプル探すか... ない... ソース読むか...」(時間の無駄)・Anorm のコンセプト “You don’t need another DSL

to access relational databases.” 自体には共感する・RDB 使ってて SQL と手を切れるなんて幻想ですよね

35

ScalikeJDBC

・SQL 書きたいときは sql”select name from

companies where id = ${id}” のように書く・SQLInterpolation では必ずバインド変数になるので

SQL インジェクション脆弱性がないと断言できる・1.6 から select.from(Company as c).where.eq(c.id,

123) のように SQL から乖離しない DSL をサポート・QueryDSL は IDE での補完にも強い(Dynamic +

macros に時代が追いついてないけど)

36

SQLInterpolation

val ids = Seq(1, 2, 3) case class Member(id: Long, name: Option[String]) DB readOnly { implicit s => sql”select id, name from members where id in (${ids})” .map { rs => Member(rs.long(“id”), rs.stringOpt(“name”)) } .list.apply() } object Member extends SQLSyntaxSupport[Member] val m = Member.syntax(“m”) DB readOnly { implicit s => sql”select ${m.result.*} from ${Member as m} where ${m.id} in (${ids})” .map { rs => Member(rs.long(“id”), rs.stringOpt(“name”)) } .list.apply()}

37

QueryDSL

val ids = Seq(1, 2, 3) case class Member(id: Long, name: Option[String]) object Member extends SQLSyntaxSupport[Member] val m = Member.syntax(“m”) DB readOnly { implicit s => withSQL { select.from(Members as m).where.in(m.id, ids) } .map { rs => Member( id = rs.long(m.resultName.id), name = rs.stringOpt(m.resultName.name)) } .list.apply() }

38

「あれ?わかりやすい!」「めんどくさくなさそう!」

(心の声)

39

いろんな SQL を...

// Squeryl join(Student, Club.leftOuter)((s, c) => where(c.map(_.id) isNull) select(s) on(s.clubId === c.map(_.id)))

// Slick for { (s, c) <- Student leftJoin Club.map(_.??) on (_.clubId === _._1) if c._1.isNull } yield (s, c)

// ScalikeJDBC val (s, c) = (Student.syntax(“s”), Club.syntax(“c”)) select.from(Student as s).leftJoin(Club as c).on(s.clubId, c.id).where.isNull(c.id)

select * from students s left join clubs c on s.club_id = s.id where c.id is null

40

 one-to-x API

・one-to-x API が実は結構便利・sql”...”.one(..).toMany(...).list.apply() のようにして複数のテーブルを join してもきれいに取ってこれる・one-to-one は無制限だが one-to-manies は現状だと最大 5 個のテーブルまで結合可能という制限がある(実装依存なので必要なら要望ください)

41

one-to-x 例 val pg: Option[Programmer] = withSQL { select.from(Programmer as p) .leftJoin(Company as c).on(p.companyId, c.id) .leftJoin(ProgrammerSkill as ps).on(ps.programmerId, p.id) .leftJoin(Skill as s).on(sqls.eq(ps.skillId, s.id).and.isNull(s.deletedAt)) .where.eq(p.id, id).and.isNull(p.deletedAt) }.one(Programmer(p, c)) .toMany(Skill.opt(s)) .map { (pg, skills) => pg.copy(skills = skills) } .single.apply()

// one(...).toOne(...) // one(...).toManies(...)

42

Debugging & Testing

・ソースコードを自動生成する sbt プラグインもある(sbt “scalikejdbc-gen [table-name]”)・ActiveRecord のようなクエリログ出力、ログ出力だけでなく Fluentd など外部に送ったりもできる・テスト重視、AutoRollback と Fixture を ScalaTest、spec2 に提供している

43

クエリログ

[debug] s.StatementExecutor$$anon$1 - SQL execution completed

[Executed SQL] select * from users where email = 'guillaume@sample.com'; (2 ms)

[Stack Trace] ... models.User$.findByEmail(User.scala:26) controllers.Projects$$anonfun$index$1$$anonfun$apply$1$$anonfun$apply$2.apply(Projects.scala:20) controllers.Projects$$anonfun$index$1$$anonfun$apply$1$$anonfun$apply$2.apply(Projects.scala:19) controllers.Secured$$anonfun$IsAuthenticated$3$$anonfun$apply$3.apply(Application.scala:88) controllers.Secured$$anonfun$IsAuthenticated$3$$anonfun$apply$3.apply(Application.scala:88) play.api.mvc.Action$$anon$1.apply(Action.scala:170)

・遅いクエリの内容だけでなく、そのクエリがコードのどこで発行されたかまで分かる

44

ScalaTest 例

class MemberSpec extends fixture.FlatSpec with AutoRollback { // fixture loading before each test override def fixture(implicit session: DBSession) { sql”insert into members values (1, ‘Alice’)”.update.apply() }

it should “work” in { implicit session => // do something and will be rolled back } }

・fixture パッケージの Spec であれば AutoRollback

trait を mixin して fixture と auto rollback が可能

45

specs2 例

object MemberSpec extends Specification { trait AutoRollbackWithFixture extends AutoRollback { // fixture loading before each test override def fixture(implicit session: DBSession) { sql”insert into members values (1, ‘Alice’)”.update.apply() } }

“it should work” in new AutoRollbackWithFixture { // do something and will be rolled back }

・unit は以下の通り、acceptance スタイルもサポート(ただし case class xxx() というちょっと変態的な...)

46

Go Reactive!・non-blocking な ScalikeJDBC-Async もあるよ・@mauricio の postgresql/mysql-async

・直接使うと結構めんどうなのを頑張って吸収している・トランザクションも map/for 式 で表現可能・JDBC ほど枯れてないので、ガチで使うなら人柱覚悟で

49

気に入ったら今すぐに http://git.io/scalikejdbc へ行って

star を押してください!!!

スター乞食

50

普通の Web アプリ開発

51

Play2 で管理画面?

・正直、いろいろ無理あるよね...

・Servlet 捨てなくていいとき多いよね...

・Play2 は C10K 問題を克服するための次世代の Web フレームワークを目指してわざわざリライトされたことを思い出そう(Play の ML で宣言)・個人的には Play2 は JSON API サーバなどが最も適していると思っている

52

なら Scalatra で

・Sinatra よりは機能が豊富とはいえ、同じジレンマ・色々やり方決めたり、他のものと組み合わせたり・・・管理画面みたいなお決まりのものなら、色々考えたり、決めごとつくったり、検証したりせずに済ませたい・でもフレームワークによる制限があると困る

53

Go Skinny!

・”Scala on Rails” を目指している Web フレームワーク(成り立ちはどちらかというと Padrino 的)・Scalatra + ScalikeJDBC を土台とし、それらをスムーズに連携させる glue code でできている・ View Template の Precompile を否定、コンパイル待ちを減らしてサクサク開発できることを重視・テスタビリティを重視(session の mock もサポート)

54

 Rails 的 Controller

・Controller は ScalatraFilter の拡張、Rails のアレがだいたいある(まだないものもこれから随時対応)・Validation は Command ではなく独自・Controller にルーティング設定を埋め込まず、メソッド定義するだけにするスタイルを推奨・SkinnyResource という Rails の ActiveResource 相当もあって ROA なアプリならこれが楽、Rails scaffold 互換で XML、JSON レスポンスの URL まで自動生成

55

Controller & Routing class MembersController extends SkinnyController { def index = { set(“members”, Member.findAll()) // set in the request scope render(“/members/index”) // expects /views/members/index.html.ssp } }

val members = new MembersController with Routes { get(“/members/?”)(index).as(‘index) // ‘index: action name } class ScalatraBootstrap extends SkinnyLifeCycle { override def initSkinnyApp(ctx: ServletContext) { ctx.mount(members, “/*”) } }

56

View

// WEB-INF/views/members/index.html.ssp <%@val members: Seq[Member] %> <ul> #for (member <- members) <li>${member.name}</li> #end </ul>

// WEB-INF/views/members/index.html.scaml -@val members: Seq[Member] %ul - for (member <- members) %li #{member.name}

57

before/afterAction

class MembersController extends SkinnyController { protectFromForgery() beforeAction(only = Seq(‘index)) { set(“members”, Member.findAll()) } def index = { render(“/members/index”) } }

val members = new MembersController with Routes { get(“/members/?”)(index).as(‘index) }

58

Form/Validation class MembersController extends SkinnyController { def createForm = validation( paramKey(“name”) is required, paramKey(“companyId”) is required & numeric)

def create = { if (createForm.validate()) { val id = Member.createWithPermittedAttributes(params.permit( “name” -> ParamType.String, “companyId” -> ParamType.Long)) flash += “notice” -> “Created!” redirect(s”/members/${id}”) } else { render(“/members/new”) } } }

59

「あれ?わかりやすい!」「めんどくさくなさそう!」

(心の声)

60

SkinnyResource object MembersController extends SkinnyResource { protectFromForgery() override def model = Member override def resourcesName = “members” override def resourceName = “member” override def createForm = validation(paramKey(“name”) is required) override def createFormStrongParameters = Seq(“name” -> ParamType.String) override def updateForm = validation(paramKey(“name”) is required) override def updateFormStrongParameters = Seq(“name” -> ParamType.String) }

class ScalatraBootstrap extends SkinnyLifeCycle { override def initSkinnyApp(ctx: ServletContext) { ctx.mount(MembersController, “/*”) } }

61

SkinnyResource URIs

GET /members GET /members/ GET /members.xml GET /members.json GET /members/{id} GET /members/{id}.xml GET /members/{id}.json GET /members/new POST /members POST /members/ GET /members/{id}/edit POST /members/{id} PUT /members/{id} PATCH /members/{id} DELETE /members/{id}

62

SkinnyResource Views

src/main/webapp/WEB-INF layouts/default.ssp views/members/index.html.ssp views/members/new.html.ssp views/members/edit.html.ssp views/members/show.html.ssp

・SkinnyResource の場合は、以下のような命名規約で

ssp/scaml/jade などのファイルを置く

63

「クソ便利すぎワロタwww」「めんどくさくなさそう!」

(心の声)

64

65

 Skinny ORM

・ActiveRecord 的な簡便さを追求・単体でも利用可能(Play2 でも使える)・裏側は ScalikeJDBC、その機能もフルで使える・var なしで entity を定義できる・Association はなるべく join でとってくる思想・Eager loading、ネストした Association は未対応・FactoryGirl(DB テストデータ作成サポート)・DBSeeds でお手軽 migration、ちゃんとしたのも予定

66

Model case class Member(id: Long, name: Option[String]) object Member extends SkinnyCRUDMapper[Member] { def extract(rs: WrappedResultSet, m: ResultName[Member]) = new Member( id = rs.long(m.id), name = rs.stringOpt(m.name) ) } Member.createWithAttribute(‘name -> “Alice”)

Member.findAllBy(sqls”name is not null”) Member.where(‘name -> “Alice”).limit(10).offset(0) val member = Member.findById(123) member.map(_.copy(name = “Bob”).save())

Member.updateById(123).withAttributes(‘name -> “Bob”) Member.deleteById(234)

67

FactoryGirl

// src/test/resources/factories.conf member { name=”Anonymous” }

// XXXSpec.scala val anon: Member = FactoryGirl(Member).create() val chris: Member = FactoryGirl(Member).create(‘name -> “Chris”) val factory = FactoryGirl(Member).withValues( ‘createdAt -> new DateTime(2011, 6, 22, 13, 46)) val eric: Member = factory.create(‘name -> “Eric”)

・factory_pal は instance をつくるだけ、こっちは DB にデータ投入もしてくれる(本来の #create)

68

Associations

case class Member(id: Long, name: Option[String], companyId: Option[Long], company: Option[Company] = None, skills: Seq[Skill] = Nil)

object Member extends SkinnyCRUDMapper[Member] { belongsTo[Company](Company, (m, c) => m.copy(company = c)).byDefault val skills = hasManyThough[Skill](MemberSkill, Skill, (m, ss) => m.copy(skills ss)) def extract(rs: WrappedResultSet, m: ResultName[Member]) = new Member( rs.long(m.id), rs.stringOpt(m.name), rs.longOpt(m.companyId) ) } Member.findById(123) // with company Member.joins(Member.skills).findById(123) // with company, skills

69

 Skinny の今後・C10K クリアしないとまずいなら Typesafe Platform で・Scala に Rails 必要?Rails と同じくらい楽に実装できて、かつ型があれば相当嬉しいはずと思っている・認証 API デザインとか手伝ってくれる人募集中・私が仕事に投入したら API を fix して 1.0 にする予定(おそらく遅くとも今年度中)・Better Java としての Scala にはまだまだ可能性ある

70

気に入ったら今すぐにhttp://git.io/skinny へ行ってstar を押してください!!!

スター乞食

71

宣伝のまとめ

・ScalikeJDBC は現在 1.6.10、業務利用実績多数で十分に stable、production ready です・ScalikeJDBC-Async は現在 0.2.8、まだ α version で本番実績は少ない、人柱上等な人が使ってください・Skinny Framework は 0.9.4、おおまかなアーキテクチャはほぼ固まったが、内部 API 改善と機能追加の真っ最中、2013 4Q までには 1.0 を出したいと思ってます

72

ぼっちはイヤだ!!!一緒に捗る Scala をつくりましょう!

73

Recommended