Slide 1

Slide 1 text

4PMJEBOE4VTUBJOBCMF %FWFMPQNFOUJO4DBMB ,B[VIJSP4FSB!TFSBUDI 4DBMJLF+%#$4LJOOZ'SBNFXPSL 'PVOEFS-FBE%FWFMPQFS

Slide 2

Slide 2 text

"TL.F-BUFS wNBHTGPSRVFTUJPOFSTBUUIFFOEPGUIJT TFTTJPO%PO`UNJTTJU 2

Slide 3

Slide 3 text

8IP"N* w,B[VIJSP4FSB w!TFSBUDIPO5XJUUFS(JU)VC w4DBMBFOUIVTJBTUTJODF w4DBMJLF+%#$ 4LJOOZ'SBNFXPSL "84DBMB GPVOEFSQSPKFDUMFBE w"XFCEFWFMPQFSBU. *OD 8F`SFB(PME 4QPOTPS

Slide 4

Slide 4 text

*OUSPEVDF .Z 0VS 1SPEVDUT

Slide 5

Slide 5 text

4DBMJLF+%#$ wTDBMJLFKECDPSH wl4DBMBMJLF+%#$z w1SPWJEFT4DBMBJTI"1*T w4UBSUFEBTBCFUUFSRVFSVMPVT"OPSN wl+VTUXSJUF42-BOEHFUUIJOHTEPOFz w2VFSZ%4-GPSTNPPUIOFTTUZQFTBGFUZ w4UBCMFFOPVHIMPUTPGDPNQBOJFTBMSFBEZ VTFJUJOQSPEVDUJPO

Slide 6

Slide 6 text

%FQFOEFODJFT // build.sbt or project/Build.scala! ! scalaVersion := “2.11.2” // or “2.10.4”! ! libraryDependencies ++= Seq(! “org.scalikejdbc” %% “scalikejdbc” % “2.1.1”,! “com.h2database” % “h2” % “1.4.181”,! “ch.qos.logback” % “logback-classic” % “1.1.2”! )

Slide 7

Slide 7 text

#BTJD6TBHF import scalikejdbc._! ! ConnectionPool.singleton(! “jdbc:h2:mem:matsuri”, ! “user”, “secret”)! ! DB autoCommit { implicit session =>! sql”create table attendee (name varchar(32))”.execute.apply()! val name = “seratch”! sql”insert into attendee (name) values ($name)”.update.apply()! }! ! val names: Seq[String] = DB readOnly { implicit s =>! sql”select name from attendee”! .map(_.string(“name”)).list.apply()! } SQL statement! (PreparedStatement) execute/update! (JDBC operation) Side effect ! with DB connection Extractor

Slide 8

Slide 8 text

2VFSZ%4- import scalikejdbc._! case class Attendee(name: String)! object Attendee extends SQLSyntaxSupport[Attendee] {! def apply(rs: WrappedResultSet, a: ResultName[Attendee]) = ! new Attendee(rs.get(a.name))
 }! ! implicit val session = AutoSession! ! ! val a = Attendee.syntax(“a”)! val seratch: Option[Attendee] = withSQL {! select.from(Attendee as a).where.eq(a.name, “seratch”)! }.map(rs => new Attendee(rs, a)).single.apply()! ! // select a.name as n_on_a from attendee a where a.name = ? QueryDSL! (Mostly SQL) Actual SQL Query

Slide 9

Slide 9 text

4LJOOZ'SBNFXPSL wTLJOOZGSBNFXPSLPSH wl4DBMBPO3BJMTz w'PS3BJMT1MBZMPWFST wXBTPVUPOUI.BSDI w"MSFBEZTFWFSBMFYQFSJFODFTJOQSPEVDUJPO w'VMMTUBDLGFBUVSFT8FCJOGSBTUSVDUVSF  4DB⒎PMEHFOFSBUPS 03. %#NJHSBUJPO  +40/TUV⒎ )551DMJFOU .BJMTFOEFS +PC XPSLFST "TTFUTDPOUSPMMFS FUD

Slide 10

Slide 10 text

#PPUJONJOVUFT ! // install skinny command! brew tap skinny-framework/alt! brew install skinny! ! // create app and start! skinny new myapp! cd myapp! skinny run! ! // access localhost:8080 from your browser!

Slide 11

Slide 11 text

.PEFM %"0 import skinny.orm._! import scalikejdbc._! ! case class User(id: Long, name: Option[String])! ! // companion: data mapper! object User extends SkinnyCRUDMapper[User] {! def defaultAlias = createAlias(“u”)! def extract(rs: WrappedResultSet, u: ResultName[User])! = autoConstruct(rs, u)
 }! ! User.findById(123)! User.count()! User.createWithAttributes(‘name -> “Alice”)! User.updateById(123).withAttributes(‘name -> “Bob”)! User.deleteBy(sqls.eq(u.name, “Bob”)) CRUD Mapper Object! (No need to be companion) Entity Smooth APIs

Slide 12

Slide 12 text

$POUSPMMFS 3PVUF package controller! class UsersController extends ApplicationController {! def showUsers = {! set(“users”, User.findAll())! render(“/users/index”)
 }
 }! ! // Routings! object Controllers {! val users = new UsersController with Routes {! get(“/users/”)(showUsers).as(‘showUsers)
 }! def mount(ctx: ServletContext): Unit = {! users.mount(ctx)! }
 } Set value in request scope! (Visible in views) Expects “src/main/webapp/! WEB-INF/views/users/index.html.ssp”

Slide 13

Slide 13 text

7JFX5FNQMBUF // src/main/webapp/WEB-INF/views/users/index.html.ssp! ! <%@val users: Seq[User] %>! ! ! #for (user <- users)! ! ! ! ! #end! ${user.id}${user.name} import from request scope Loop, if/else syntax! in Scalate

Slide 14

Slide 14 text

8FC%FWFMPQNFOU w*OUFSBDUJWFGFFECBDLMPPQJTNPTU JNQPSUBOUFTQFDJBMMZXIFODIBOHJOH6* w"DUVBMMZ4DBMBDPNQJMBUJPOJTTPTMPXUIBU XBJUJOHWJFXUFNQMBUFTDPNQJMBUJPONBLFT EFWFMPQFSTNVDITUSFTTFE w4LJOOZEPFTO`UDPNQJMFBMMUIFWJFX UFNQMBUFTXIFOEFWFMPQJOH VOMJLF5XJSM

Slide 15

Slide 15 text

.Z(PPE1BSUT GPS4PMJEBOE4BGF %FWFMPQNFOU

Slide 16

Slide 16 text

.Z(PPE1BSUT w4JNQMJpFE$MBTTCBTFE001"OE *NNVUBCMF%BUB4USVDUVSF w8PSLJOH0O1SPCMFNT8JUIPVU 0WFSLJMM"CTUSBDUJPO w8SJUJOH5FTUT8JUIPVU2VFTUJPO w,FFQ*OGSBTUSVDUVSF-JHIUXFJHIU w/P4VSQSJTFT'PS/FXDPNFS

Slide 17

Slide 17 text

4JNQMJpFE$MBTT CBTFE001 "OE*NNVUBCMF %BUB4USVDUVSF

Slide 18

Slide 18 text

$MBTTCBTFE001 w"MSFBEZTPQPQVMBS +BWB 3VCZ 1ZUIPO  w0METUZMFJTGSJFOEMZXJUINVUBCJMJUZ FH TFUUFST CBOHNFUIPET CVUUIBU`TOPUB QSFSFRVJTJUF w001DBOCFNPSFTPMJEBOETBGFSCZ LFFQJOHJNNVUBCJMJUZBOEBWPJEJOH JOIFSJUBODFBOUJQBUUFSOT

Slide 19

Slide 19 text

4DBMBPS+BWB w4DBMBDBTFDMBTTJTTJNQMFSUIBO+BWB CFBOTXJUI HSFBU -PNCPL w4DBMBIJHIPSEFSGVODUJPOTBSFTJNQMFS UIBO+BWB4USFBN"1* w*NNVUBCJMJUZJTXFMMUSFBUFEJO4DBMB w'BJSOFTT+BWBEFDJTJWFMZCFBUT4DBMBJO DPNQBSJTPOXJUIDPNQJMBUJPOTQFFE

Slide 20

Slide 20 text

*NNVUBCJMJUZ w%PBXBZXJUINVUBCMFTUBUFT w3FBTTJHONFOU /PXBZ%PO`UVTFAWBSA w*NNVUBCJMJUZNBLFTZPVSBQQTOPUPOMZ TDBMBCMFCVUBMTPNPSFTPMJE w8IFOZPVNPEJGZBDBTFDMBTT DBMM DPQZ BOESFUVSOOFXTUBUFJOTUFBEPG VTJOHTFUUFSTGPSNVUBCJMJUZJOTJEF

Slide 21

Slide 21 text

*NNVUBCMF&OUJUZ // entity with behaviors! case class User(id: Long, name: Option[String])! extends SkinnyRecord[User] {! override def skinnyCRUDMapper = User
 }! // data mapper! object User extends SkinnyCRUDMapper[User] {! override def defaultAlias = createAlias(“u”)! override def extract(rs: WrappedResultSet, u: ResultName[User])! = autoConstruct(rs, u)
 }! ! val noNames: Seq[User] = User.where(‘name -> “”).apply()! val anons: Seq[User] = noNames.map { user => ! user.copy(name = “Anonymous”).save()! } Both of “noNames” and “anons” are immutable

Slide 22

Slide 22 text

5SBJU$IBPT w.JYJOHUSBJUTDBOTIPXZPVUFSSJCMFDIBPT w8FTIPVMEIBWFTFMGEJTDJQMJOF w1SFGFSAPWFSSJEFANPEJpFSUPEFUFDU"1* DIBOHFTXIFONJYJOHNBOZUSBJUT w$PMMFDUUIFTBNFTPSUPGUSBJUTBOEQMBDF UIFNJOTBNFQMBDFUPBWPJEDPEF EVQMJDBUJPOPSVOXBOUFEDPNQMFYJUZ

Slide 23

Slide 23 text

8FC$POUSPMMFS package controller! class MainController extends ApplicationController ! with concern.TimeLogging {! def index = logElapsedTime { render(“/main/index”) }! }! ! // for controllers! package controller.concern! trait TimeLogging { self: SkinnyController with Logging =>! def millis: Long = System.currentTimeMillis ! def logElapsedTime[A](action: => A): A = {! val before = millis! val result = action! logger.debug(“Elapsed time: ${millis - before} millis”)! result
 }! } The “concern” just follows Rails style. Anyway, naming should be simple and! easy-to-understand for anyone

Slide 24

Slide 24 text

$PEJOH4UZMF5JQT w6TFTCUTDBMBSJGPSNXJUIPVURVFTUJPO TJNJMBSHPMBOH`TGNU  w%PO`UUPTTTJNJMBSDMBTTFTPSUSBJUTJOUP TJOHMFTDBMBpMFFYDFQUATFBMFEAQBUUFSO w%PO`UQMBDFDMBTTFTVOEFSEJ⒎FSFOU QBDLBHFEJSFDUPSZ BMUIPVHIJU`TQPTTJCMF  w%PZPVSFBMMZOFFEDBLFQBUUFSO  w1SFGFSFMPRVFOUNFUIPETJHOBUVSFUIBO FYQMBJOJOHBMPUJOTDBMBEPDT

Slide 25

Slide 25 text

8PSLJOH0O 1SPCMFNT8JUIPVU 0WFSLJMM"CTUSBDUJPO

Slide 26

Slide 26 text

5IF3FBM"T*T w"CTUSBDUJPOPGUFOJNQSPWFTUIJOHT CVU UIBU`TOPUBMXBZTUIFCFTUXBZUPTPMWF SFBMXPSMEQSPCMFNT w*0JTTVFJTBUZQJDBMDBTFUIBUXFTIPVME DPNQSFIFOEQSPCMFNTBTJT w%BUBCBTFBDDFTT42-JTOPUBDPMMFDUJPO CVUKVTUBOFYUFSOBM*0PQFSBUJPO w0WFSLJMMBCTUSBDUJPONBLFTZPVSDPEF EJ⒏DVMUUPNBJOUBJOGPSPUIFSEFWFMPQFST

Slide 27

Slide 27 text

%PO`U)JEFUIF42- wl:PVEPO`UOFFEBOPUIFS%4-UPBDDFTT SFMBUJPOBMEBUBCBTFTz"OPSN w:PVNVTUSFDPHOJ[FXIBUJTXPSLJOH F⒎FDUJWFMZJOUIF42-MBZFS w6UJMJUZUPXSJUF%"0FBTJMZJTpOFCVU IJEJOH42-JTOPUHPPE w/FFEUPHSBCUIFDBVTFGSPNSBXRVFSJFT XIFOEFBMJOHXJUIUSPVCMFT DPNGPSUBCMF MPHHJOHBMTPDBOIFMQ

Slide 28

Slide 28 text

42-0QT"T*T // A programmer belongs to a company and has several skills! ! implicit val session = AutoSession! ! val p: 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(ps.skillId, s.id)! .where.eq(p.id, 123).and.isNull(p.deletedAt)! }! .one(rs => Programmer(rs, p, c))! .toMany(rs => Skill.opt(rs, s))! .map { (programmer, skills) => programmer.copy(skills = skills) }! .single! .apply() I believe everybody can understand this code Extracts one-to-many relationships here

Slide 29

Slide 29 text

4LJOOZ03. w03.CVJMUPO4DBMJLF+%#$ w)JHIMZJOTQJSFECZ3BJMT"DUJWF3FDPSE w42-RVFSJFTGPS$36%BQQTBSFDPNNPO FOPVHI TPJU`TSFBTPOBCMFUPBWPJEXSJUJOH NPTUMZTBNFDPEFFWFSZXIFSF w4LJOOZ03.EPFTOUQSFWFOUZPVGSPN VTJOH4DBJLF+%#$"1*TEJSFDUMZ w"QBSUPG4LJOOZ'SBNFXPSLCVUZPVDBO VTFJUJO1MBZBQQTUPP

Slide 30

Slide 30 text

%FQFOEFODJFT // build.sbt or project/Build.scala! ! scalaVersion := “2.11.2” // or “2.10.4”! ! libraryDependencies ++= Seq(! //“org.scalikejdbc” %% “scalikejdbc” % “2.1.1”,! “org.skinny-framework” %% “skinny-orm” % “1.3.1”,! “com.h2database” % “h2” % “1.4.181”,! “ch.qos.logback” % “logback-classic” % “1.1.2”! )

Slide 31

Slide 31 text

.BQQFST // entities! case class Company(id: Long, name: String)! case class Employee(id: Long, name: String,! companyId: Long, company: Option[Company])! ! // mappers! object Company extends SkinnyCRUDMapper[Company] {! def extract(rs: WrappedResultSet, rn: ResultName[Company]) =! autoConstruct(rs, rn)
 }! object Employee extends SkinnyCRUDMapper[Employee] {! def extract(rs: WrappedResultSet, rn: ResultName[Employee]) =! autoConstruct(rs, rn, “company”)! // simple association definition! lazy val companyRef = belongsTo[Company](! Company, (e, c) => e.copy(company = c))
 }

Slide 32

Slide 32 text

3FBTPOBCMF ! ! val companyId = Company.createWithAttributes(‘name -> “Sun”)! val empId = Employee.createWithAttributes(! ‘name -> “Alice”, ‘companyId -> companyId)! ! val emp: Option[Employee] = Employee.findById(empId)! val empWithCompany: Option[Employee] = ! Employee.joins(companyRef).findById(123)! ! Company.updateById(companyId).withAttributes(‘name -> “Oracle”)! ! val e = Employee.defaultAlias! Employee.deleteBy(sqls.eq(e.id, empId))! Company.deleteById(companyId) Using ScalikeJBDC API! is also possible Right, these CRUD operations are not SQL-ish. However, I believe they’re reasonable because these patterns are already common enough.

Slide 33

Slide 33 text

8SJUJOH5FTUT 8JUIPVU 2VFTUJPO

Slide 34

Slide 34 text

/PU0OMZ$PNQJMFS w*U`TUSVFUIBUDPNQJMFSIFMQTZPVCZ EFUFDUJOHNJTUBLFTJODPEJOH w8SJUJOHUFTUTJTBSFBTPOBCMFXBZUPWFSJGZ ZPVSDPEFNFFUTSFRVJSFNFOUT TQFDJpDBUJPOTBTFYQFDUFE w:PVDBO`UTLJQBVUPNBUFEUFTUTFWFOJG ZPVSBQQTBSFXSJUUFOJO4DBMB

Slide 35

Slide 35 text

TDPWFSBHF w"UUIJTUJNF UIFPOMZPQUJPOBWBJMBCMFGPS VTJTTDPWFSBHF 4$$5JOIFSJUPS  w"EEUIFEFQFOEFODZJOUPZPVSQSPKFDUT SJHIUOPXJGZPVEPO`UVTFJUZFU

Slide 36

Slide 36 text

,FFQ *OGSBTUSVDUVSF -JHIUXFJHIU

Slide 37

Slide 37 text

"WPJE4#5)BDLT wTCUJTOPUTPFBTZGPS4DBMBOFXCJFT  VQHSBEJOHTCUJTBMMUIFNPSFTP w1MBZEFQFOETPOTCUWFSTJPO FH1MBZ XTCU VQHSBEJOH1MBZJTBCPVUOPU POMZ1MBZ"1*DIBOHFTCVUTCUUIJOHT w:PVSPXOTCUQMVHJOTIBDLTNBLFZPVS QSPKFDUTEJ⒏DVMUUPNBJOUBJOGPSBMPOH QFSJPEBOEJOWPMWFPUIFST w%PO`UUSZUPEPFWFSZUIJOHUIFSF

Slide 38

Slide 38 text

4LJOOZ5BTL3VOOFS w+VTUXBOUBTJNQMFBOElSBLFzMJLFUBTL SVOOFS OPTCUQMVHJO  w4JNQMJTUJDCVUQSBHNBUJDJEFBlNBJO$MBTTz PGlUBTLzTCUQSPKFDUDBOCFEJTQBUDIFSPG UBTLSVOOFSTZTUFN w5BTLTBSFXSJUUFOJO4DBMB OPTCUQMVHJO  w/PUPOMZXSJUJOHDPEFCVUVQHSBEJOH TDBMBTCUWFSTJPOCFDPNFQSFUUZFBTZ

Slide 39

Slide 39 text

5BTLT // sbt settings! // mainClass := Some("TaskRunner")! ! // task/src/main/scala/TaskRunner.scala! object TaskRunner extends skinny.task.TaskLauncher {! register("assets:precompile", (params) => {! val buildDir = params.headOption.getOrElse(“build")! // AssetsPrecompileTask is a Scala object! skinny.task.AssetsPrecompileTask.main(Array(buildDir))! })! }! ! // skinny task:run assets:precompile [dir] Pure Scala function! task name mainClass as the dispatcher

Slide 40

Slide 40 text

6TF0OMZ&TTFOUJBMT w5IFTBNFJTTVFBT3VCZHFNT XIBU`T XPSTF 4DBMB`TFDPTZTUFNJTTUJMMTP TNBMMFSUIBO3VCZ`TPOF w#JOBSZJODPNQBUJCJMJUZNBLFTFYJTUJOH MJCSBSJFTPVUEBUFEFWFSZ4DBMBNBKPS WFSTJPOSFMFBTF w"SFZPVSFBMMZSFBEZUPGPSLUIFN  w6TJOH+BWBBTTFUT FHDPNNPOT  JOUFSOBMMZJTBMTPXPSUIUIJOLJOHBCPVU

Slide 41

Slide 41 text

/P4VSQSJTFT GPS/FXDPNFS

Slide 42

Slide 42 text

$BO'FFM8FMDPNF w*TKPJOJOHZPVS4DBMBQSPKFDUTFBTZ $BO OFXDPNFSVOEFSTUBOEPWFSWJFXBUPODF  w%PO`UTUJDLUPEPJOHPOUIFTCU EPO`U EJTGBWPSVTJOHPUIFSUPPMT FH(SVOU  w8FMMLOPXOTUZMF FH3BJMTTUZMF JT QSFGFSSFEGPSDPMMBCPSBUJWFXPSLT w*TBTZODISPOPTJUZSFBMMZSFRVJSFEOPX  w*OEFFE ZPVS%4-JTWFSZTJNQMFCVUFBTZ UPNPEJGZUIFN GPSPUIFST

Slide 43

Slide 43 text

/FXDPNFSTNBZOPU LOPX4DBMBXFMM "UUSBDUUIFNUP4DBMB %PO`UTDBSFUIFN

Slide 44

Slide 44 text

"."4DBMB.BUTVSJ w6ODPOGFSFODFUPNPSSPX wl"TL.F"OZUIJOHz w4DBMJLF+%#$ w4LJOOZ'SBNFXPSL w"84DBMB w"OZUIJOHFMTF

Slide 45

Slide 45 text

2VFTUJPOT

Slide 46

Slide 46 text

2VFTUJPOT

Slide 47

Slide 47 text

2VFTUJPOT

Slide 48

Slide 48 text

5IBOLT w"NB[JOHl.BUVSJ6SBLBUBz UIF DPOGFSFODFWPMVOUFFSTUB⒎  w(SFBUJOWJUFETQFBLFST w:PVBMMIFSF