ng with
2.0




      Siarzh Miadzvedzeu @siarzh
is

“a web framework for a new era”

                   &

“designed by web developers
 for web developers”
                   Guillaume Bort, creator
0.x   how it was
1.0   how it was
1.2   how it was
2.0   how it was
2.0            is

•   full stack web framework for JVM
•   high-productive
•   asynch & reactive
•   stateless
•   HTTP-centric
•   typesafe
•   scalable
•   open source
•   part of Typesafe Stack 2.0
2.0             is fun and high-productive


•   fast turnaround: change you code and hit reload! :)
•   browser error reporting
•   db evolutions
•   modular and extensible via plugins
•   minimal bootstrap
•   integrated test framework
•   easy cloud deployment (e.g. Heroku)
2.0            is asynch & reactive


•   WebSockets
•   Comet
•   HTTP 1.1 chuncked responses
•   composable streams handling
•   based on event-driven, non-blocking Iteratee I/O
2.0            is HTTP-centric


•   based on HTTP and stateless
•   doesn't fight HTTP or browser
•   clean & easy URL design (via routes)
•   designed to work with HTML5

          “When a web framework starts an architecture fight
          with the web, the framework loses.”
2.0                 is typesafe where it matters




                                      }
•   templates
•   routes
•   configs
•   javascript (via Goggle Closure)
                                          all are compiled
•   LESS stylesheets
•   CoffeeScript

                + browser error reporting
2.0                 getting started

1. download Play 2.0 binary package


2. add play to your PATH:
                              export PATH=$PATH:/path/to/play20


3. create new project:
                              $ play new myFirstApp



4. run the project:
                              $ cd myFirstApp
                              $ play run
2.0                  config
                                             single conf/application.conf:
# This is the main configuration file for the application.

# The application languages
# ~~~~~
application.langs="en"

# Database configuration
# ~~~~~
# You can declare as many datasources as you want.
# By convention, the default datasource is named `default`
#
db.default.driver=org.h2.Driver
db.default.url="jdbc:h2:mem:play"
# db.default.user=sa
# db.default.password=

# Evolutions
# ~~~~~
# You can disable evolutions if needed
# evolutionplugin=disabled
...
2.0        IDE support
Eclipse:             $ eclipsify


IntelliJ IDEA:       $ idea



Netbeans:        add to plugins.sbt:
                 resolvers += {
                     "remeniuk repo" at "http://remeniuk.github.com/maven"
                 }


                 libraryDependencies += {
                   "org.netbeans" %% "sbt-netbeans-plugin" % "0.1.4"
                 }



                     $ play netbeans
2.0                  and SBT
              Build.scala                                     plugins.sbt
import sbt._                                  addSbtPlugin("play" % "sbt-plugin" % "2.0")
import Keys._
import PlayProject._

object ApplicationBuild extends Build {

val appName            = "Your application"
val appVersion         = "1.0"

val appDependencies = Seq(
 // Add your project dependencies here,
)

val main = PlayProject(
 appName, appVersion, appDependencies,
  mainLang = SCALA
).settings(
 // Add your own project settings here
)

}
2.0               routes
# Home page
GET /                     controllers.Application.homePage()
GET /home                 controllers.Application.show(page = "home")

# Display a client.
GET /clients/all          controllers.Clients.list()
GET /clients/:id          controllers.Clients.show(id: Long)

# Pagination links, like /clients?page=3
GET /clients              controllers.Clients.list(page: Int ?= 1)

# With regex
GET /orders/$id<[0-9]+>   controllers.Orders.show(id: Long)

# 'name' is all the rest part of the url including '/' symbols
GET /files/*name          controllers.Application.download(name)
2.0                reversed routing

# Hello action
GET /helloBob    controllers.Application.helloBob
GET /hello/:name controllers.Application.hello(name)




// Redirect to /hello/Bob
def helloBob = Action {
  Redirect( routes.Application.hello("Bob") )
}
2.0                   actions
action: (play.api.mvc.Request => play.api.mvc.Result)


Action(parse.text) { request =>
  Ok("<h1>Got: " + request.body + "</h1>").as(HTML).withSession(
                session + ("saidHello" -> "yes")
              ).withHeaders(
                CACHE_CONTROL -> "max-age=3600",
                ETAG -> "xx"
              ).withCookies(
                Cookie("theme", "blue")
              )
}

val   notFound = NotFound
val   pageNotFound = NotFound(<h1>Page not found</h1>)
val   badRequest = BadRequest(views.html.form(formWithErrors))
val   oops = InternalServerError("Oops")
val   anyStatus = Status(488)("Strange response type")
2.0                controllers

package controllers

import play.api.mvc._

object Application extends Controller {

    def index = Action {
      Ok("It works!")
    }

    def hello(name: String) = Action {
      Ok("Hello " + name)
    }

    def goodbye(name: String) = TODO

}
2.0              templates
                                                      …are just functions ;)
views/main.scala.html:     @(title: String)(content: Html)
                           <!DOCTYPE html>
                           <html>
                           <head>
                            <title>@title</title>
                           </head>
                           <body>
                            <section class="content">@content</section>
                           </body>
                           </html>

views/hello.scala.html:    @(name: String = “Guest”)

                           @main(title = "Home") {
                           <h1>Welcome @name! </h1>
                           }




then from Scala class:     val html = views.html.Application.hello(name)
2.0                database evolutions
conf/evolutions/${x}.sql:


        # Add Post

        # --- !Ups
        CREATE TABLE Post (
          id bigint(20) NOT NULL AUTO_INCREMENT,
          title varchar(255) NOT NULL,
          content text NOT NULL,
          postedAt date NOT NULL,
          author_id bigint(20) NOT NULL,
          FOREIGN KEY (author_id) REFERENCES User(id),
          PRIMARY KEY (id)
        );

        # --- !Downs
        DROP TABLE Post;
2.0   browser error reporting
2.0                   access SQL data via Anorm
import anorm._

DB.withConnection { implicit c =>

    val selectCountries = SQL("Select * from Country")

    // Transform the resulting Stream[Row] to a List[(String,String)]
    val countries = selectCountries().map(row =>
      row[String]("code") -> row[String]("name")
    ).toList
}


// using Parser API
import anorm.SqlParser._

val count: Long = SQL("select count(*) from Country").as(scalar[Long].single)

val result:List[String~Int] = {
  SQL("select * from Country")
  .as(get[String]("name")~get[Int]("population") map { case n~p => (n,p) } *)
}
2.0                     and Akka
 def index = Action {          // using actors, coverting Akka Future to Play Promise
  Async {
         (myActor ? "hello").mapTo[String].asPromise.map { response =>
           Ok(response)
         }
     }
 }


 def index = Action {          // execute some task asynchronously
  Async {
         Akka.future { longComputation() }.map { result =>
           Ok("Got " + result)
         }
     }
 }

// schedule sending message 'tick' to testActor every 30 minutes
Akka.system.scheduler.schedule(0 seconds, 30 minutes, testActor, "tick")

// schedule a single task
Akka.system.scheduler.scheduleOnce(10 seconds) {
  file.delete()
}
2.0                 streaming response
def index = Action {

    val file = new java.io.File("/tmp/fileToServe.pdf")
    val fileContent: Enumerator[Array[Byte]] = Enumerator.fromFile(file)

  SimpleResult(
    header = ResponseHeader(200, Map(CONTENT_LENGTH ->
file.length.toString)),
        body = fileContent
    )
}


// Play has a helper for the above:


def index = Action {
  Ok.sendFile(new java.io.File("/tmp/fileToServe.pdf"))
}
2.0                  chunked results
def index = Action {

    val data = getDataStream
    val dataContent: Enumerator[Array[Byte]] = Enumerator.fromStream(data)

    ChunkedResult(
     header = ResponseHeader(200),
        chunks = dataContent
    )
}


// Play has a helper for the ChunkedResult above:

Ok.stream(dataContent)
2.0                  Comet
lazy val clock: Enumerator[String] = {
  val dateFormat = new SimpleDateFormat("HH mm ss")

     Enumerator.fromCallback { () =>
       Promise.timeout(Some(dateFormat.format(new Date)), 100 milliseconds)
     }
 }

 def liveClock = Action {
   Ok.stream(clock &> Comet(callback = "parent.clockChanged"))
 }

and then in template:
<script type="text/javascript" charset="utf-8">
 var clockChanged = function(time) { // do something }
</script>

<iframe id="comet" src="@routes.Application.liveClock.unique"></iframe>
2.0             WebSockets

just another action in controller:

   def index = WebSocket.using[String] { request =>

       // Log events to the console
       val in = Iteratee.foreach[String](println).mapDone { _ =>
         println("Disconnected")
       }

       // Send a single 'Hello!' message
       val out = Enumerator("Hello!")

       (in, out)
   }
2.0   caching API
      by default uses EHCache, configurable via plugins


      Cache.set("item.key", connectedUser)


      val user: User = Cache.getOrElse[User]("item.key") {
        User.findById(connectedUser)
      }



      // cache HTTP response
      def index = Cached("homePage",600) {
        Action {
          Ok("Hello world")
        }
      }
2.0                 i18n

conf/application.conf:   application.langs="en,en-US,fr"


conf/messages.en:
                         home.title=File viewer
                         files.summary=The disk {1} contains {0} file(s).



 from Scala class:       val title   = Messages("home.title")
                         val titleFR = Messages("home.title")(Lang(“fr"))
                         val summary = Messages("files.summary", d.files.length, d.name)

  from template:         <h1>@Messages("home.title")</h1>
2.0                        testing
                                                   …using specs2 by default
"Computer model" should {


    "be retrieved by id" in {
     running(FakeApplication(additionalConfiguration = inMemoryDatabase())) {

            val Some(macintosh) = Computer.findById(21)


            macintosh.name must equalTo("Macintosh")
            macintosh.introduced must beSome.which(dateIs(_, "1984-01-24"))

        }
    }
}
2.0             testing templates


 "render index template" in {
   val html = views.html.index("Coco")

     contentType(html) must equalTo("text/html")
     contentAsString(html) must contain("Hello Coco")
 }
2.0               testing controllers


"respond to the index Action" in {
  val result = controllers.Application.index("Bob")(FakeRequest())

    status(result) must equalTo(OK)
    contentType(result) must beSome("text/html")
    charset(result) must beSome("utf-8")
    contentAsString(result) must contain("Hello Bob")
}
2.0              testing routes

"respond to the index Action" in {
  val Some(result) = routeAndCall(FakeRequest(GET, "/Bob"))

    status(result) must equalTo(OK)
    contentType(result) must beSome("text/html")
    charset(result) must beSome("utf-8")
    contentAsString(result) must contain("Hello Bob")
}
2.0               testing server


"run in a server" in {
  running(TestServer(3333)) {

        await( WS.url("http://localhost:3333").get ).status must equalTo(OK)

    }
}
2.0                 testing with browser
                                 …using Selenium WebDriver with FluentLenium

"run in a browser" in {
    running(TestServer(3333), HTMLUNIT) { browser =>

        browser.goTo("http://localhost:3333")
        browser.$("#title").getTexts().get(0) must equalTo("Hello Guest")

        browser.$("a").click()

        browser.url must equalTo("http://localhost:3333/Coco")
        browser.$("#title").getTexts().get(0) must equalTo("Hello Coco")


    }
}
2.0   Demo
2.0         Resources

http://www.playframework.org/
https://github.com/playframework/Play20/wiki
http://www.parleys.com/#st=5&id=3143&sl=4
http://www.parleys.com/#st=5&id=3144&sl=13
http://www.parleys.com/#id=3081&st=5
http://vimeo.com/41094673
2.0   Questions


         ?

Play!ng with scala

  • 1.
    ng with 2.0 Siarzh Miadzvedzeu @siarzh
  • 2.
    is “a web frameworkfor a new era” & “designed by web developers for web developers” Guillaume Bort, creator
  • 3.
    0.x how it was
  • 4.
    1.0 how it was
  • 5.
    1.2 how it was
  • 6.
    2.0 how it was
  • 7.
    2.0 is • full stack web framework for JVM • high-productive • asynch & reactive • stateless • HTTP-centric • typesafe • scalable • open source • part of Typesafe Stack 2.0
  • 8.
    2.0 is fun and high-productive • fast turnaround: change you code and hit reload! :) • browser error reporting • db evolutions • modular and extensible via plugins • minimal bootstrap • integrated test framework • easy cloud deployment (e.g. Heroku)
  • 9.
    2.0 is asynch & reactive • WebSockets • Comet • HTTP 1.1 chuncked responses • composable streams handling • based on event-driven, non-blocking Iteratee I/O
  • 10.
    2.0 is HTTP-centric • based on HTTP and stateless • doesn't fight HTTP or browser • clean & easy URL design (via routes) • designed to work with HTML5 “When a web framework starts an architecture fight with the web, the framework loses.”
  • 11.
    2.0 is typesafe where it matters } • templates • routes • configs • javascript (via Goggle Closure) all are compiled • LESS stylesheets • CoffeeScript + browser error reporting
  • 12.
    2.0 getting started 1. download Play 2.0 binary package 2. add play to your PATH: export PATH=$PATH:/path/to/play20 3. create new project: $ play new myFirstApp 4. run the project: $ cd myFirstApp $ play run
  • 13.
    2.0 config single conf/application.conf: # This is the main configuration file for the application. # The application languages # ~~~~~ application.langs="en" # Database configuration # ~~~~~ # You can declare as many datasources as you want. # By convention, the default datasource is named `default` # db.default.driver=org.h2.Driver db.default.url="jdbc:h2:mem:play" # db.default.user=sa # db.default.password= # Evolutions # ~~~~~ # You can disable evolutions if needed # evolutionplugin=disabled ...
  • 14.
    2.0 IDE support Eclipse: $ eclipsify IntelliJ IDEA: $ idea Netbeans: add to plugins.sbt: resolvers += { "remeniuk repo" at "http://remeniuk.github.com/maven" } libraryDependencies += { "org.netbeans" %% "sbt-netbeans-plugin" % "0.1.4" } $ play netbeans
  • 15.
    2.0 and SBT Build.scala plugins.sbt import sbt._ addSbtPlugin("play" % "sbt-plugin" % "2.0") import Keys._ import PlayProject._ object ApplicationBuild extends Build { val appName = "Your application" val appVersion = "1.0" val appDependencies = Seq( // Add your project dependencies here, ) val main = PlayProject( appName, appVersion, appDependencies, mainLang = SCALA ).settings( // Add your own project settings here ) }
  • 16.
    2.0 routes # Home page GET / controllers.Application.homePage() GET /home controllers.Application.show(page = "home") # Display a client. GET /clients/all controllers.Clients.list() GET /clients/:id controllers.Clients.show(id: Long) # Pagination links, like /clients?page=3 GET /clients controllers.Clients.list(page: Int ?= 1) # With regex GET /orders/$id<[0-9]+> controllers.Orders.show(id: Long) # 'name' is all the rest part of the url including '/' symbols GET /files/*name controllers.Application.download(name)
  • 17.
    2.0 reversed routing # Hello action GET /helloBob controllers.Application.helloBob GET /hello/:name controllers.Application.hello(name) // Redirect to /hello/Bob def helloBob = Action { Redirect( routes.Application.hello("Bob") ) }
  • 18.
    2.0 actions action: (play.api.mvc.Request => play.api.mvc.Result) Action(parse.text) { request => Ok("<h1>Got: " + request.body + "</h1>").as(HTML).withSession( session + ("saidHello" -> "yes") ).withHeaders( CACHE_CONTROL -> "max-age=3600", ETAG -> "xx" ).withCookies( Cookie("theme", "blue") ) } val notFound = NotFound val pageNotFound = NotFound(<h1>Page not found</h1>) val badRequest = BadRequest(views.html.form(formWithErrors)) val oops = InternalServerError("Oops") val anyStatus = Status(488)("Strange response type")
  • 19.
    2.0 controllers package controllers import play.api.mvc._ object Application extends Controller { def index = Action { Ok("It works!") } def hello(name: String) = Action { Ok("Hello " + name) } def goodbye(name: String) = TODO }
  • 20.
    2.0 templates …are just functions ;) views/main.scala.html: @(title: String)(content: Html) <!DOCTYPE html> <html> <head> <title>@title</title> </head> <body> <section class="content">@content</section> </body> </html> views/hello.scala.html: @(name: String = “Guest”) @main(title = "Home") { <h1>Welcome @name! </h1> } then from Scala class: val html = views.html.Application.hello(name)
  • 21.
    2.0 database evolutions conf/evolutions/${x}.sql: # Add Post # --- !Ups CREATE TABLE Post ( id bigint(20) NOT NULL AUTO_INCREMENT, title varchar(255) NOT NULL, content text NOT NULL, postedAt date NOT NULL, author_id bigint(20) NOT NULL, FOREIGN KEY (author_id) REFERENCES User(id), PRIMARY KEY (id) ); # --- !Downs DROP TABLE Post;
  • 22.
    2.0 browser error reporting
  • 23.
    2.0 access SQL data via Anorm import anorm._ DB.withConnection { implicit c => val selectCountries = SQL("Select * from Country") // Transform the resulting Stream[Row] to a List[(String,String)] val countries = selectCountries().map(row => row[String]("code") -> row[String]("name") ).toList } // using Parser API import anorm.SqlParser._ val count: Long = SQL("select count(*) from Country").as(scalar[Long].single) val result:List[String~Int] = { SQL("select * from Country") .as(get[String]("name")~get[Int]("population") map { case n~p => (n,p) } *) }
  • 24.
    2.0 and Akka def index = Action { // using actors, coverting Akka Future to Play Promise Async { (myActor ? "hello").mapTo[String].asPromise.map { response => Ok(response) } } } def index = Action { // execute some task asynchronously Async { Akka.future { longComputation() }.map { result => Ok("Got " + result) } } } // schedule sending message 'tick' to testActor every 30 minutes Akka.system.scheduler.schedule(0 seconds, 30 minutes, testActor, "tick") // schedule a single task Akka.system.scheduler.scheduleOnce(10 seconds) { file.delete() }
  • 25.
    2.0 streaming response def index = Action { val file = new java.io.File("/tmp/fileToServe.pdf") val fileContent: Enumerator[Array[Byte]] = Enumerator.fromFile(file) SimpleResult( header = ResponseHeader(200, Map(CONTENT_LENGTH -> file.length.toString)), body = fileContent ) } // Play has a helper for the above: def index = Action { Ok.sendFile(new java.io.File("/tmp/fileToServe.pdf")) }
  • 26.
    2.0 chunked results def index = Action { val data = getDataStream val dataContent: Enumerator[Array[Byte]] = Enumerator.fromStream(data) ChunkedResult( header = ResponseHeader(200), chunks = dataContent ) } // Play has a helper for the ChunkedResult above: Ok.stream(dataContent)
  • 27.
    2.0 Comet lazy val clock: Enumerator[String] = { val dateFormat = new SimpleDateFormat("HH mm ss") Enumerator.fromCallback { () => Promise.timeout(Some(dateFormat.format(new Date)), 100 milliseconds) } } def liveClock = Action { Ok.stream(clock &> Comet(callback = "parent.clockChanged")) } and then in template: <script type="text/javascript" charset="utf-8"> var clockChanged = function(time) { // do something } </script> <iframe id="comet" src="@routes.Application.liveClock.unique"></iframe>
  • 28.
    2.0 WebSockets just another action in controller: def index = WebSocket.using[String] { request => // Log events to the console val in = Iteratee.foreach[String](println).mapDone { _ => println("Disconnected") } // Send a single 'Hello!' message val out = Enumerator("Hello!") (in, out) }
  • 29.
    2.0 caching API by default uses EHCache, configurable via plugins Cache.set("item.key", connectedUser) val user: User = Cache.getOrElse[User]("item.key") { User.findById(connectedUser) } // cache HTTP response def index = Cached("homePage",600) { Action { Ok("Hello world") } }
  • 30.
    2.0 i18n conf/application.conf: application.langs="en,en-US,fr" conf/messages.en: home.title=File viewer files.summary=The disk {1} contains {0} file(s). from Scala class: val title = Messages("home.title") val titleFR = Messages("home.title")(Lang(“fr")) val summary = Messages("files.summary", d.files.length, d.name) from template: <h1>@Messages("home.title")</h1>
  • 31.
    2.0 testing …using specs2 by default "Computer model" should { "be retrieved by id" in { running(FakeApplication(additionalConfiguration = inMemoryDatabase())) { val Some(macintosh) = Computer.findById(21) macintosh.name must equalTo("Macintosh") macintosh.introduced must beSome.which(dateIs(_, "1984-01-24")) } } }
  • 32.
    2.0 testing templates "render index template" in { val html = views.html.index("Coco") contentType(html) must equalTo("text/html") contentAsString(html) must contain("Hello Coco") }
  • 33.
    2.0 testing controllers "respond to the index Action" in { val result = controllers.Application.index("Bob")(FakeRequest()) status(result) must equalTo(OK) contentType(result) must beSome("text/html") charset(result) must beSome("utf-8") contentAsString(result) must contain("Hello Bob") }
  • 34.
    2.0 testing routes "respond to the index Action" in { val Some(result) = routeAndCall(FakeRequest(GET, "/Bob")) status(result) must equalTo(OK) contentType(result) must beSome("text/html") charset(result) must beSome("utf-8") contentAsString(result) must contain("Hello Bob") }
  • 35.
    2.0 testing server "run in a server" in { running(TestServer(3333)) { await( WS.url("http://localhost:3333").get ).status must equalTo(OK) } }
  • 36.
    2.0 testing with browser …using Selenium WebDriver with FluentLenium "run in a browser" in { running(TestServer(3333), HTMLUNIT) { browser => browser.goTo("http://localhost:3333") browser.$("#title").getTexts().get(0) must equalTo("Hello Guest") browser.$("a").click() browser.url must equalTo("http://localhost:3333/Coco") browser.$("#title").getTexts().get(0) must equalTo("Hello Coco") } }
  • 37.
    2.0 Demo
  • 38.
    2.0 Resources http://www.playframework.org/ https://github.com/playframework/Play20/wiki http://www.parleys.com/#st=5&id=3143&sl=4 http://www.parleys.com/#st=5&id=3144&sl=13 http://www.parleys.com/#id=3081&st=5 http://vimeo.com/41094673
  • 39.
    2.0 Questions ?