beginning scala with skinny framework #jjug_ccc

46
Beginning Scala with Skinny Framework Kazuhiro Sera @seratch M3, Inc. Skinny Framework Team JJUG CCC 2014 Spring 2014/05/18

Upload: kazuhiro-sera

Post on 18-Dec-2014

477 views

Category:

Technology


5 download

DESCRIPTION

Talk about Skinny Framework at Japan Java User Group's Meetup

TRANSCRIPT

Page 1: Beginning Scala with Skinny Framework #jjug_ccc

Beginning Scala with Skinny Framework

Kazuhiro Sera @seratch M3, Inc. Skinny Framework Team JJUG CCC 2014 Spring 2014/05/18

Page 2: Beginning Scala with Skinny Framework #jjug_ccc

- What’s Skinny -

Page 3: Beginning Scala with Skinny Framework #jjug_ccc

Skinny?

・”Skinny” has a nice ring. !

・Application should be skinny ・Framework should be skinny ・”su-ki-ni” in Japanese means “as you like it”

Page 4: Beginning Scala with Skinny Framework #jjug_ccc

Skinny Framework・2014/03/28 - version 1.0.0 (latest 1.0.14) ・Concept: Scala on Rails ・Full-stack Web app framework ・MVC、ORM、DB migration、Mail Sender ・scaffold auto generator ・Run on the Servlet containers ・Java 1.6 or higher, Servlet 3.0 or higher ・war packaging, standalone jar file

Page 5: Beginning Scala with Skinny Framework #jjug_ccc

Full-stack required?

・Full-stack web framework is a dead word? ・Recently they’re not uncommon ・Integrating libraries is flexible but costs ・Recommended way to do the same is important for team building ・Developers’ time should be used for creating differentiated features

Page 6: Beginning Scala with Skinny Framework #jjug_ccc

Scala on Rails?

・Rails and Play1 are optimised for HTML-based web app development ・Play2 is a new generation framework to implement servers that have high performance and solve C10K problem ・JSON API servers、Akka-oriented apps !

Page 7: Beginning Scala with Skinny Framework #jjug_ccc

Skinny Structure・Basically built upon dependable libraries ・Routes, Servlet API DSL from Scalatra ・Default template engine: Scalate ・ORM is built upon ScalikeJDBC ・DB migration: Flyway ・JSON operations: json4s ・Input validations and mailer are original !

Page 8: Beginning Scala with Skinny Framework #jjug_ccc

- Beginning Skinny -

Page 9: Beginning Scala with Skinny Framework #jjug_ccc

Bootstrap (1)・Required: Only JDK (1.6 or higher) ・Download a zip file from skinny-framework.org !

!

!

!

!

Page 10: Beginning Scala with Skinny Framework #jjug_ccc

Bootstrap (2)・Unzip it and run “skinny run” ・Access localhost:8080 from your browser ・Same procedure on Windows OS !

!

!

!

!

!

Page 11: Beginning Scala with Skinny Framework #jjug_ccc

Bootstrap (3)・Recommended sbt project template ・All the jar files are already downloaded ・You don’t need learning sbt settings in detail at this point ・Easy to run on Jenkins ・Using IntelliJ IDEA is recommended (Community Edition is enough for Scala) !

Page 12: Beginning Scala with Skinny Framework #jjug_ccc

- Skinny MVC -

Page 13: Beginning Scala with Skinny Framework #jjug_ccc

・#set puts value to request scope (can be accessed in controller/view) ・Returned value will be the response body !

!

!

!

!

!

First Controller (1)

// src/main/scala/controller/RootController.scala!!package controller!import skinny._!class RootController extends ApplicationController {! def index = {! set(“name”, “JJUG”)! render(“/root/index”)! }!}

Page 14: Beginning Scala with Skinny Framework #jjug_ccc

・”Hello ${name}!” will be “Hello JJUG!” !

!

!

!

!

!

!

!

First Controller (2)

! -# src/main/webapp/WEB-INF/views/root/index.html.ssp!<%@val name: String %>!<h1>Hello, ${name}!</h1>

Page 15: Beginning Scala with Skinny Framework #jjug_ccc

・#set can accept various types of values ・In this case, Scala collection object !

!

!

!

!

!

!

First Controller (3)

// src/main/scala/controller/RootController.scala!!package controller!import skinny._!class RootController extends ApplicationController {! def index = {! set(“numbers”, Seq(1,2,3,4,5))! render(“/root/index”)! }!}!

Page 16: Beginning Scala with Skinny Framework #jjug_ccc

・Loop/if-else with ${numbers} !

!

!

!

!

!

!

!

!

First Controller (4)

! -# src/main/webapp/WEB-INF/views/root/index.html.ssp!!<%@val numbers: Seq[Int] %>!#for (n <- numbers)! #if (n % 2 == 0)! ${n}! #end!#end!

Page 17: Beginning Scala with Skinny Framework #jjug_ccc

・Scalatra’s Servlet wrapper API provides easy-to-use DSL ・e.g.) status = 201, redirect(url), halt(400) ・Also possible to use Servlet API directly ・If you’re familiar with Servlet, it’s very easy to understand !

!

First Controller (5)

Page 18: Beginning Scala with Skinny Framework #jjug_ccc

・Skinny’s Routing uses Scalatra DS (Routes trait extends a little) ・Substance of controller is Servet’s Filter/Servlet, so just mount it to ServletContext !

!

!

!

!

First Routing (1)

! // src/main/scala/controller/Controllers.scala!! object Controllers {!! object root extends RootController with Routes {!! val indexUrl = get(“/“)(index).as(‘index)! }!! override def mount(ctx: ServletContext): Unit = {!! root.mount(ctx)!! }!! }!

Page 19: Beginning Scala with Skinny Framework #jjug_ccc

・s.url(Controllers.root.indexUrl) returns actual URL string ・Avoid embedding URL in view templates !

!

!

!

!

!

First Routing (2)

! // src/main/scala/controller/Controllers.scala!! object Controllers {!! object root extends RootController with Routes {!! val indexUrl = get(“/“)(index).as(‘index) ! }!! // …!! }!

Page 20: Beginning Scala with Skinny Framework #jjug_ccc

・skinny-validator: input validator DSL ・If errors, they’re already put into request scope, so just show them in the form. !

!

!

!

!

!

!

SkinnyValidator

! class MembersController extends ApplicationController {!! protectFromForgery()!! def createForm = validation(params,!! paramKey(“name”) is required & maxLength(64),!! paramKey(“email”) is email!! )!! def create = {!! if (createForm.validate()) {!! doCreate(params.permit(!! “name” -> ParamType.String, “email” -> ParamType.String))! } else {!! render(“/members/input”)!! }!! }!! }

Page 21: Beginning Scala with Skinny Framework #jjug_ccc

・Default: Scalate SSP(Scala Server Pages) ・Scalate supports SSP, Scaml(≒Haml), Jade and Mustache ・If you use other templates, just change file extension (e.g. show.html.ssp -> show.html.jade) ・FreeMarker and Thymeleaf are also supported (optional)

First View

Page 22: Beginning Scala with Skinny Framework #jjug_ccc

・DB access example with Skinny ORM ・case class: entity, object: DAO !

!

!

!

!

!

First Model (1)

! sql”create table member (id serial, name varchar(64))”!! ! .execute.apply()! !!! // src/main/scala/model/Member.scala!! case class Member(id: Long, name: Option[String])!!! object Member extends SkinnyCRUDMapper[Member] {!! lazy val defaultAlias = createAlias(“m”)!! def extract(rs: WrappedResultSet, m: ResultName[Member]) =!! new Member(rs.get(m.id), rs.get(m.name))!! }

Page 23: Beginning Scala with Skinny Framework #jjug_ccc

・DB migration by using Flyway is handy to setup local dev environment !

!

!

!

!

!

!

First Model (2)

! ./skinny g migration createMember!! // src/main/resources/db/migration/V20140514141738__createMember.sql!! // Write DDL to V20140514141738__createMember.sql!!! // “development” ENVB!! ./skinny db:migrate!!! // “test” ENV!! ./skinny db:migrate test

Page 24: Beginning Scala with Skinny Framework #jjug_ccc

・That’s all, now you can use APIs !

!

!

!

!

!

!

!

First Model (3)

! // Insert!! val id: Long = Member.createWithAttributes('name -> “JJUG")!! // insert into member (name) values ('JJUG')!!! // Finder API!! val ms: Seq[Member] = Member.findAll()!! // select m.id as i_on_m, m.name as n_on_m from member m order by m.id;!!! // Querying API!! val ms: Seq[Member] = Member.where(‘name -> “JJUG").apply()!! // from member m where m.name = ‘JJUG'!!! // ScalikeJDBC QueryDSL!! val m = Member.defaultAlias!! val mopt: Option[Member] = Member.findBy(sqls.eq(m.name, "JJUG"))!! // from member m where m.name = ‘JJUG'

Page 25: Beginning Scala with Skinny Framework #jjug_ccc

・Add “country" table !

!

!

!

!

!

!

First Model (4)

! case class Member(id: Long, name: Option[String])!! object Member extends SkinnyCRUDMapper[Member] {!! lazy val defaultAlias = createAlias(“m”)!! def extract(rs: WrappedResultSet, m: ResultName[Member] =!! new Member(rs.get(m.id), rs.get(m.name))!! }!!! // create table country(id serial, name varchar(128) not null);!! object class Country(id: Long, name: String)!! object Country extends SkinnyCRUDMapper[Country] {!! lazy val defaultAlias = createAlias(“m”)!! def extract(rs: WrappedResultSet, c: ResultName[Country] =!! new Country(rs.get(c.id), rs.get(c.name))!! }

Page 26: Beginning Scala with Skinny Framework #jjug_ccc

・Add a belongsTo replationship !

!

!

!

!

!

!

!

First Model (5)

! case class Member(!! id: Long, !! name: Option[String]!! countryId: Option[Long],!! country: Option[Country] = None)!!! object Member extends SkinnyCRUDMapper[Member] {!! val defaultAlias = createAlias(“m”)!! def extract(rs: WrappedResultSet, m: ResultName[Member] =!! new Member(rs.get(m.id), rs.get(m.name), rs.get(m.countryId))!!! val country = belongsTo[Country](Country, !! (member, country) => member.copy(country = country)!! )!! }

Page 27: Beginning Scala with Skinny Framework #jjug_ccc

・Calling #joins resolves associations !

!

!

!

!

!

!

!

First Model(6)

! // Insert!! val countryId: Long = Country.createWithAttributes('name -> “Japan”)!! val id = Member.createWithAttributes(!! ‘name -> “JJUG”, ‘countryId -> countryId)!!! // member だけ!! val m = Member.findById(id)!! // from member m where m.id = ?!!! // country も!! Member.joins(Member.country).findById(id)!! // from member m left join country c on m.country_id = c.id where m.id = ?!

Page 28: Beginning Scala with Skinny Framework #jjug_ccc

・#byDefault allows fetching associations without calling #joins !

!

!

!

!

!

!

!

First Model (7)

! case class Member(!! id: Long, !! name: Option[String]!! countryId: Option[Long],!! country: Option[Country] = None)!!! object Member extends SkinnyCRUDMapper[Member] {!! val defaultAlias = createAlias(“m”)!! def extract(rs: WrappedResultSet, m: ResultName[Member] =!! new Member(rs.get(m.id), rs.get(m.name), rs.get(m.countryId))!!! val country = belongsTo[Country](Country, !! (member, country) => member.copy(country = country)!! ).byDefault!! }

Page 29: Beginning Scala with Skinny Framework #jjug_ccc

・AutoSession is the simplest way ・Transaction is provided by ScalikeJDBC’s localTx blocks or it’s fine to use TxPerRequestFilter(thread-local) !

!

!

!

!

!

First Model (8)

! DB.localTx { implicit session =>!! // will be rolled back when exception is thrown here!! Member.findById(id).map { member =>!! member.copy(name = newName).save() // SkinnyRecord!! MemberStatus.setAsActive(member)! }.getOrElse {!! val id = Member.createWithAttributes(‘name -> newName)!! MemberStatus.addNewMemberAsActive(id)! }!! }

Page 30: Beginning Scala with Skinny Framework #jjug_ccc

・Just use in controllers ・Avoid fat controller !

!

!

!

!

!

!

First Model (9)

! // src/main/scala/controller/MembersController.scala!!! import model.Member!! class MembersController extends ApplicationController {!! def showAll = {!! set(“members”, Member.findAll())!! render(“/members/showAll”)!! }!! }!!

Page 31: Beginning Scala with Skinny Framework #jjug_ccc

FactoryGirl・Highly inspired by thoughtbot/factory_girl ・Not YAML but typesafe-config’s HOCON ・Scala code can be executed !

!

!

!

!

!

! member {!! name=“JJUG”!! luckyNumber="${scala.util.Random.nextInt(64)}"!! }

! val member: Member = FactoryGirl(Member).create()!! val member = FactoryGirl(Member).create(‘name -> “ScalaJP”)

Page 32: Beginning Scala with Skinny Framework #jjug_ccc

- Other Features -

Page 33: Beginning Scala with Skinny Framework #jjug_ccc

scaffold (1)・CRUD admin pages by scaffold ・Test code also generated !

!

!

!

!

!

!

! ./skinny g scaffold members member name:String birthday:Option[LocalDate] active:Boolean!!! *** Skinny Generator Task ***!!! "src/main/scala/controller/ApplicationController.scala" skipped.!! "src/main/scala/controller/MembersController.scala" created.!! "src/main/scala/controller/Controllers.scala" modified.!! "src/test/scala/controller/MembersControllerSpec.scala" created.!! "src/test/scala/integrationtest/MembersController_IntegrationTestSpec.scala" created.!! "src/test/resources/factories.conf" modified.!! "src/main/scala/model/Member.scala" created.!! "src/test/scala/model/MemberSpec.scala" created.!! "src/main/webapp/WEB-INF/views/members/_form.html.ssp" created.!! "src/main/webapp/WEB-INF/views/members/new.html.ssp" created.!! "src/main/webapp/WEB-INF/views/members/edit.html.ssp" created.!! "src/main/webapp/WEB-INF/views/members/index.html.ssp" created.!! "src/main/webapp/WEB-INF/views/members/show.html.ssp" created.!! "src/main/resources/messages.conf" modified.!! "src/main/resources/db/migration/V20140514173530__Create_members_table.sql" created.

Page 34: Beginning Scala with Skinny Framework #jjug_ccc

scaffold (2)! ./skinny db:migrate !! ./skinnny run!!! ./skinny db:migrate test!! ./skinny test

Page 35: Beginning Scala with Skinny Framework #jjug_ccc

・Validation code is also generated !

!

!

!

!

!

!

!

scaffold (3)

Page 36: Beginning Scala with Skinny Framework #jjug_ccc

・flash is also available !

!

!

!

!

!

!

!

scaffold (4)

Page 37: Beginning Scala with Skinny Framework #jjug_ccc

・pagination also implemented !

!

!

!

!

!

!

!

scaffold (5)

Page 38: Beginning Scala with Skinny Framework #jjug_ccc

reverse-scaffold・scaffold generation from existing DB ・Simple primary key is required !

!

!

!

!

!

!

! ./skinny g reverse-scaffold members members member!!! *** Skinny Reverse Engineering Task ***!!! Table : members!! ID : id:Long!! Resources : members!! Resource : member!!! Columns:!! - name:String:varchar(512)!! - birthday:Option[LocalDate]!! - createdAt:DateTime!! - updatedAt:DateTime!!! *** Skinny Generator Task ***!!! "src/main/scala/controller/ApplicationController.scala" skipped.!! "src/main/scala/controller/MembersController.scala" created.!! "src/main/scala/controller/Controllers.scala" modified.

Page 39: Beginning Scala with Skinny Framework #jjug_ccc

・JavaMail DSL ・Available for not only Skinny apps !

!

!

!

!

!

!

!

SkinnyMailer

! val config = SkinnyMailerConfig.default.copy(!! // JavaMail, SMTP configuration !! )!! val mailer = SkinnyMailer(config)!! mailer!! .to(“[email protected]”)!! .cc(“[email protected]”)!! .subject(“Skinny talk at JJUG CCC”)!! .body {“””Hi all,!! |I’ll give a talk on Skinny next Sunday.!! |”””.stripMargin}!! .deliver()

Page 40: Beginning Scala with Skinny Framework #jjug_ccc

Assets (1)・Converts CoffeeScript, React JS, Scala.js, Sass, LESS to JS/CSS ・Everything but Sass works on the JVM = Windows OS friendly ・Source Maps with native compilers ・CoffeeScript under src/main/webapp/WEB-INF/assets/coffee will be converted under src/main/webapp/assets/js

Page 41: Beginning Scala with Skinny Framework #jjug_ccc

Assets(2)・Scala.js is pretty interesting though it’s stilll in the alpha stage !

!

!

!

!

!

!

! ./skinny run!! ! ! // Terminal A!! ./skinny scalajs:watch! // Terminal B!! vim src/main/webapp/WEB-INF/assets/scala/Sample.scala! // Terminal C

Page 42: Beginning Scala with Skinny Framework #jjug_ccc

Deployment・”skinny package” generates war file ・package task compiles all the Scalate templates ・”skinny package:standalone” generates jar file which has an embedded Jetty server ・Heroku deployment is also ready !

!! ./skinny package!! ./skinny package:standalone

Page 43: Beginning Scala with Skinny Framework #jjug_ccc

- Conclusion -

Page 44: Beginning Scala with Skinny Framework #jjug_ccc

Skinny for You・Skinny is the best framework when you need Play1/Rails in Scala ・Much simpler code than Java apps ・Skinny ORM + ScalikeJDBC is so useful ・I think negative things about Scala development are too long compilation time, learning tools and styles. Skinny resolves or reduces in a pragmatic way

Page 45: Beginning Scala with Skinny Framework #jjug_ccc

Not Only “Reactive”・Reactive applications is the most exciting trend in the Scala community ・OTOH, statically-typed Ruby-like code or Java compatible and simpler code is much desired by lots of people ・You can use Skinny(Scalatra)with Akka but Skinny’s features focuses on “Better Java” convenience

Page 46: Beginning Scala with Skinny Framework #jjug_ccc

Roadmap

・Skinny 1.1 - Scala 2.10/2.11 cross build, deps major version up ・We’ll keep compatible APIs ・We’d like to challenge non-blocking APIs too, we hope doing that with http4s or something similar