Upload
kazuhiro-sera
View
4.560
Download
1
Embed Size (px)
DESCRIPTION
Presentation at ScalaMatsuri 2014 http://scalamatsuri.org/en/program/index.html#session-b-4 http://live.nicovideo.jp/watch/lv191315534#5:12:20
Citation preview
Solid and Sustainable Development in Scala
Kazuhiro Sera @seratch ScalikeJDBC / Skinny Framework
Founder & Lead Developer
Ask Me Later!
• 3 mags for questioners at the end of this session! Don’t miss it!
2
Who Am I
• Kazuhiro Sera • @seratch on Twitter/GitHub • Scala enthusiast since 2010 • ScalikeJDBC, Skinny Framework, AWScala founder & project lead
• A web developer at M3, Inc (We’re a Gold Sponsor)
Introduce My(Our) Products
ScalikeJDBC
• scalikejdbc.org • “Scala-like JDBC” • Provides Scala-ish APIs • Started as a better querulous / Anorm • “Just write SQL and get things done” • QueryDSL for smoothness & type-safety • Stable enough: lots of companies already use it in production
Dependencies
// 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”! )
Basic Usage 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
QueryDSL 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
Skinny Framework
• skinny-framework.org • “Scala on Rails” • For Rails / Play1 lovers • 1.0.0 was out on 28th March • Already several experiences in production • Full-stack features: Web infrastructure, Scaffold generator, ORM, DB migration, JSON stuff, HTTP client, Mail sender, Job workers, Assets controller, etc..
Boot in 2 minutes
! // 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!
Model (DAO) 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
Controller + Route 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”
View Template
// 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
Web Development
• Interactive feedback loop is most important especially when changing UI
• Actually Scala compilation is so slow that waiting view templates compilation makes developers much stressed
• Skinny doesn’t compile all the view templates when developing (unlike Twirl)
My Good Parts for Solid and Safe
Development
My Good Parts•Simplified Class-based OOP And Immutable Data Structure •Working On Problems Without Overkill Abstraction •Writing Tests Without Question •Keep Infrastructure Lightweight •No Surprises For Newcomer
Simplified Class-based OOP
And Immutable Data Structure
Class-based OOP
• Already so popular (Java, Ruby, Python ..) • Old style is friendly with mutability (e.g. setters, bang methods), but that’s not a prerequisite
• OOP can be more solid and safer by keeping immutability and avoiding inheritance anti-patterns
Scala or Java 8?
• Scala case class is simpler than Java beans with (great) Lombok
• Scala high-order functions are simpler than Java 8 Stream API
• Immutability is well-treated in Scala • Fairness: Java decisively beats Scala in comparison with compilation speed..
Immutability
• Do away with mutable states • Re-assignment? No way! Don’t use `var` • Immutability makes your apps not only scalable but also more solid
• When you modify a case class, call #copy() and return new state instead of using setters for mutability inside
Immutable Entity // 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
Trait Chaos
•Mixing traits can show you terrible chaos • We should have self-discipline • Prefer `override` modifier to detect API changes when mixing many traits
• Collect the same sort of traits and place them in same place to avoid code duplication or unwanted complexity
Web Controller 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
Coding Style Tips
• Use sbt-scalariform without question (similar: go-lang’s fmt)
• Don’t toss similar classes or traits into single scala file except `sealed` pattern
• Don’t place classes under different package directory (although it’s possible)
• Do you really need cake pattern? • Prefer eloquent method signature than explaining a lot in scaladocs
Working On Problems Without Overkill Abstraction
The Real As-Is
• Abstraction often improves things, but that’s not always the best way to solve real-world problems
• I/O issue is a typical case that we should comprehend problems as-is
• Database access / SQL is not a collection but just an external I/O operation
• Overkill abstraction makes your code difficult to maintain for other developers
Don’t Hide the SQL
• “You don’t need another DSL to access relational databases” - Anorm
• You must recognize what is working effectively in the SQL layer
• Utility to write DAO easily is fine but hiding SQL is not good
• Need to grab the cause from raw queries when dealing with troubles (comfortable logging also can help)
SQL Ops As-Is // 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
Skinny ORM
• ORM built on ScalikeJDBC • Highly inspired by Rails ActiveRecord • SQL queries for CRUD apps are common enough, so it’s reasonable to avoid writing mostly same code everywhere
• Skinny ORM doesn't prevent you from using ScaikeJDBC APIs directly
• A part of Skinny Framework but you can use it in Play apps too
Dependencies
// 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”! )
Mappers // 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)) }
Reasonable?!! 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.
Writing Tests Without Question
Not Only Compiler
• It’s true that compiler helps you by detecting mistakes in coding
• Writing tests is a reasonable way to verify your code meets requirements / specifications as expected
• You can’t skip automated tests even if your apps are written in Scala
scoverage• At this time, the only option available for us is scoverage (SCCT inheritor)
• Add the dependency into your projects right now if you don’t use it yet
Keep Infrastructure Lightweight
Avoid SBT Hacks
• sbt is not so easy for Scala newbies, upgrading sbt is all the more so
• Play depends on sbt version (e.g. Play 2.1 w/ sbt 0.12), upgrading Play is about not only Play API changes but sbt things
• Your own sbt plugins/hacks make your projects difficult to maintain for a long period and involve others
• Don’t try to do everything there
Skinny TaskRunner
• Just want a simple and “rake”-like task runner (no sbt plugin)
• Simplistic but pragmatic idea: “mainClass” of “task” sbt project can be dispatcher of task runner system
• Tasks are written in Scala (no sbt plugin) • Not only writing code but upgrading scala/sbt version become pretty easy
Tasks
// 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
Use Only Essentials
• The same issue as Ruby gems, what’s worse, Scala’s eco-system is still so smaller than Ruby’s one
• Binary incompatibility makes existing libraries outdated every Scala major version release
• Are you really ready to fork them? • Using Java assets (e.g. commons) internally is also worth thinking about
No Surprises for Newcomer
Can Feel Welcome?
• Is joining your Scala projects easy? Can newcomer understand overview at once?
• Don’t stick to doing on the sbt, don’t disfavor using other tools (e.g. Grunt)
• Well-known style (e.g. Rails style) is preferred for collaborative works
• Is asynchronosity really required now? • Indeed, your DSL is very simple but easy to modify them (for others)?
Newcomers may not know Scala well.
Attract them to Scala! (Don’t scare them)
AMA! #ScalaMatsuri2
• Unconference tomorrow • “Ask Me Anything” • ScalikeJDBC • Skinny Framework • AWScala • Anything else!
Questions?
Questions?
Questions?
Thanks
• Amazing “Maturi Urakata” (the conference volunteer staff)
• Great invited speakers • You all here!