1. Solid and Sustainable
Development in Scala
Kazuhiro Sera @seratch
ScalikeJDBC / Skinny Framework
Founder & Lead Developer
2. Ask Me Later!
• 3 mags for questioners at the end of this
session! Don’t miss it!
2
3. 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)
5. 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
7. Basic Usage
import scalikejdbc._!
!
ConnectionPool.singleton(!
“jdbc:h2:mem:matsuri”, !
“user”, “secret”)!
SQL statement!
(PreparedStatement)
!
DB autoCommit { implicit session =>!
Side effect !
with DB connection
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()!
}
execute/update!
(JDBC operation)
Extractor
8. 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 {!
QueryDSL!
(Mostly SQL)
Actual SQL Query
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 = ?
9. 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..
10. 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!
11. Model (DAO)
import skinny.orm._!
Entity
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)
}!
CRUD Mapper Object!
!
(No need to be companion)
User.findById(123)!
User.count()!
User.createWithAttributes(‘name -> “Alice”)!
User.updateById(123).withAttributes(‘name -> “Bob”)!
User.deleteBy(sqls.eq(u.name, “Bob”))
Smooth APIs
12. Controller + Route
package controller!
class UsersController extends ApplicationController {!
def showUsers = {!
set(“users”, User.findAll())!
render(“/users/index”)
}
}!
!
// Routings!
object Controllers {!
Set value in request scope!
(Visible in views)
Expects “src/main/webapp/!
WEB-INF/views/users/index.html.ssp”
val users = new UsersController with Routes {!
get(“/users/”)(showUsers).as(‘showUsers)
}!
def mount(ctx: ServletContext): Unit = {!
users.mount(ctx)!
}
}
14. 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)
16. 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
18. 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
19. 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..
20. 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
21. 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
22. 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
23. 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 =>!
The “concern” just follows Rails style.
Anyway, naming should be simple and!
easy-to-understand for anyone
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
}!
}
24. 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
26. 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
27. 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)
28. SQL Ops As-Is
// A programmer belongs to a company and has several skills!
!
implicit val session = AutoSession!
!
val p: Option[Programmer] = withSQL {!
I believe everybody can
understand this code
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))!
Extracts one-to-many
.toMany(rs => Skill.opt(rs, s))!
relationships here
.map { (programmer, skills) => programmer.copy(skills = skills) }!
.single!
.apply()
29. 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
31. 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))
}
32. Reasonable?
Right, these CRUD operations are not SQL-ish. However, I believe
they’re reasonable because these patterns are already common enough.
!
!
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))!
Using ScalikeJBDC API!
Company.deleteById(companyId)
is also possible
34. 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
35. 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
37. 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
38. 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
39. 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))!
})!
}!
Pure Scala function!
!
// skinny task:run assets:precompile [dir]
task name
mainClass as the dispatcher
40. 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
42. 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)?
43. Newcomers may not
know Scala well.
Attract them to Scala!
(Don’t scare them)