Se ha denunciado esta presentación.
Utilizamos tu perfil de LinkedIn y tus datos de actividad para personalizar los anuncios y mostrarte publicidad más relevante. Puedes cambiar tus preferencias de publicidad en cualquier momento.

Solid And Sustainable Development in Scala

9.395 visualizaciones

Publicado el

Presentation at ScalaMatsuri 2014

http://scalamatsuri.org/en/program/index.html#session-b-4

http://live.nicovideo.jp/watch/lv191315534#5:12:20

  • Sé el primero en comentar

Solid And Sustainable Development in Scala

  1. 1. Solid and Sustainable Development in Scala Kazuhiro Sera @seratch ScalikeJDBC / Skinny Framework Founder & Lead Developer
  2. 2. Ask Me Later! • 3 mags for questioners at the end of this session! Don’t miss it! 2
  3. 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)
  4. 4. Introduce My(Our) Products
  5. 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
  6. 6. 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”! )
  7. 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. 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. 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. 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. 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. 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)! } }
  13. 13. 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
  14. 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)
  15. 15. My Good Parts for Solid and Safe Development
  16. 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
  17. 17. Simplified Class-based OOP And Immutable Data Structure
  18. 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. 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. 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. 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. 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. 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. 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
  25. 25. Working On Problems Without Overkill Abstraction
  26. 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. 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. 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. 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
  30. 30. 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”! )
  31. 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. 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
  33. 33. Writing Tests Without Question
  34. 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. 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
  36. 36. Keep Infrastructure Lightweight
  37. 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. 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. 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. 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
  41. 41. No Surprises for Newcomer
  42. 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. 43. Newcomers may not know Scala well. Attract them to Scala! (Don’t scare them)
  44. 44. AMA! #ScalaMatsuri2 • Unconference tomorrow • “Ask Me Anything” • ScalikeJDBC • Skinny Framework • AWScala • Anything else!
  45. 45. Questions?
  46. 46. Questions?
  47. 47. Questions?
  48. 48. Thanks • Amazing “Maturi Urakata” (the conference volunteer staff) • Great invited speakers • You all here!

×