Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Solid and Sustainable Development in Scala

Kazuhiro Sera
September 06, 2014

Solid and Sustainable Development in Scala

Kazuhiro Sera

September 06, 2014
Tweet

More Decks by Kazuhiro Sera

Other Decks in Programming

Transcript

  1. %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”! )
  2. #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
  3. 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
  4. #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!
  5. .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
  6. $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”
  7. 7JFX5FNQMBUF // src/main/webapp/WEB-INF/views/users/index.html.ssp! ! <%@val users: Seq[User] %>! ! <table

    class=“table”>! #for (user <- users)! <tr>! <td>${user.id}</td>! <td>${user.name}</td>! </tr>! #end! </table> import from request scope Loop, if/else syntax! in Scalate
  8. $MBTTCBTFE001 w"MSFBEZTPQPQVMBS +BWB 3VCZ 1ZUIPO  w0METUZMFJTGSJFOEMZXJUINVUBCJMJUZ FH TFUUFST CBOHNFUIPET

    CVUUIBU`TOPUB QSFSFRVJTJUF w001DBOCFNPSFTPMJEBOETBGFSCZ LFFQJOHJNNVUBCJMJUZBOEBWPJEJOH JOIFSJUBODFBOUJQBUUFSOT
  9. *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
  10. 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
  11. 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
  12. %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”! )
  13. .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))
 }
  14. 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.
  15. 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
  16. $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