Play Framework

High Performance Scalable Web System

Disclaimer

Any similarity the following slides may have with the actual presentation are purely coincidental.

What is Play?

  • Not a library
  • Not a servlet container
  • Not Java EE

What is Play?

The Play Framework is a clean alternative to the legacy Enterprise Java stacks. It focuses on developer productivity, modern web and mobile applications, and predictable, minimal resource consumption (CPU, memory, threads) resulting in highly performant, highly scalable applications.

- http://www.typesafe.com/platform/runtime/playframework

Play is based on a lightweight, stateless, web-friendly architecture and features predictable and minimal resource consumption (CPU, memory, threads) for highly-scalable applications thanks to its reactive model, based on Iteratee IO.

- http://www.playframework.com/documentation

What does that mean?

Play supports building web services at massive scale using Java and Scala, while maintaining performance, reliability, and developer productivity.

Agenda

  • Getting Started
  • Development Process
  • Error Handling
  • Database
  • Reactive Architecture (Non-Blocking IO)
  • Web Services
  • Real-time Communications
    • Server-Sent Events
    • Websockets
  • Community

Agenda

  • Getting Started
  • Development Process
  • Error Handling
  • Database
  • Reactive Architecture (Non-Blocking IO)
  • Web Services
  • Real-time Communications
    • Server-Sent Events
    • Websockets
  • Community

Getting Started

playframework.com
activator new

activator new

activator ui

activator ui

...or you could just use your IDE

  • activator run - Start the server in development mode
  • activator stage - Generate a distribution
  • activator dist - zip the staged distribution
  • activator start - Start the server in production mode

Agenda

  • Getting Started
  • Development Process
  • Error Handling
  • Database
  • Reactive Architecture (Non-Blocking IO)
  • Web Services
  • Real-time Communications
    • Server-Sent Events
    • Websockets
  • Community

Routes


# Home page
GET     /                           controllers.Application.index()
GET     /hello                      controllers.Application.hello(name)
GET     /hello/:name                controllers.Application.hello(name)

# Map static resources from the /public folder to the /aServer-Sent Eventsts URL path
GET     /aServer-Sent Eventsts/*file               controllers.AServer-Sent Eventsts.at(path="/public", file)
                        

Controller


package controllers;

import play.*;
import play.mvc.*;

import views.html.*;

public class Application extends Controller {

    public static Result index() {
        return ok(index.render("Your new application is ready."));
    }

    public static Result hello(String name) {
        return ok("Hello, " + name);
    }
}
                        

Views


@(name: String)

<html>
    <head>
        <title>Hello, World!</title>
    </head>
    <body>
        <img src="images/jaxjug.png" alt="">
        <p>Hello, @name!</p>
    </body>
</html>
                        

Agenda

  • Getting Started
  • Development Process
  • Error Handling
  • Database
  • Reactive Architecture (Non-Blocking IO)
  • Web Services
  • Real-time Communications
    • Server-Sent Events
    • Websockets
  • Community
Controller Compilation error
View Compilation error
Routes Compilation error

Agenda

  • Getting Started
  • Development Process
  • Error Handling
  • Database
  • Reactive Architecture (Non-Blocking IO)
  • Web Services
  • Real-time Communications
    • Server-Sent Events
    • Websockets
  • Community

# 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=""
                        

conf/application.conf

Ebean

Play Framework ORM for Play/Java Projects

Define Models

ebean.default="models.*"

Create classes


package models;

import java.util.*;
import javax.persistence.*;

import play.db.ebean.*;
import play.data.format.*;
import play.data.validation.*;

@Entity
public class Task extends Model {

    @Id
    @Constraints.Min(10)
    public Long id;

    @Constraints.Required
    public String name;

    public boolean done;

    @Formats.DateTime(pattern="dd/MM/yyyy")
    public Date dueDate = new Date();

    public static Finder<Long,Task> find = new Finder<Long,Task>( Long.class, Task.class );

}
                            

Simple lookups

Task t = Task.find.byId(4l);
List<Task> ts = Task.find.all();

Powerful query language


List<Task> tasks = find.where()
    .ilike("name", "%coco%")
    .orderBy("dueDate asc")
    .findPagingList(25)
    .setFetchAhead(false)
    .getPage(1)
    .getList();
                            

Anorm

Anorm is Not an Object Relational Mapper
  • Play Framework Database Access for Play/Scala Projects
  • The best interface to a database is SQL
    
    import anorm._
    import play.api.db.DB
    
    DB.withConnection { implicit c =>
        val result: Boolean = SQL("Select 1").execute()
    }
                                        

Use {} for variable substitution


SQL(
"""
select * from Country c
join CountryLanguage l on l.CountryCode = c.Code
where c.code = {countryCode};
"""
).on("countryCode" -> "FRA")
                        

Retrieve data and process with pattern matching


case class SmallCountry(name:String)
case class BigCountry(name:String)
case class France

val countries = SQL("Select name,population from Country")().collect {
    case Row("France", _) => France()
    case Row(name:String, pop:Int) if(pop > 1000000) => BigCountry(name)
    case Row(name:String, _) => SmallCountry(name)
}
                        

Agenda

  • Getting Started
  • Development Process
  • Error Handling
  • Database
  • Reactive Architecture (Non-Blocking IO)
  • Web Services
  • Real-time Communications
    • Server-Sent Events
    • Websockets
  • Community

What is Reactive?

http://www.reactivemanifesto.org

Image courtesy of reactivemanifesto.org

How is Play Reactive?

  • Traditional web servers allocate one thread per connection
  • If things back up, more threads get allocated
  • This causes things to get worse

How is Play Reactive?

  • Play does not allocate threads per connection
  • Built on top of Netty and Akka
  • Code becomes non-blocking, allowing for more efficient use of resources

How is Play Reactive?


public static F.Promise<Result> timing() {
    long start = System.currentTimeMillis();
    F.Function<WSResponse, Long> timing = response -> System.currentTimeMillis() - start;

    F.Promise<Long> yahoo = WS.url("http://www.yahoo.com").get().map(timing);
    F.Promise<Long> google = WS.url("http://www.google.com").get().map(timing);
    F.Promise<Long> bing = WS.url("http://www.bing.com").get().map(timing);

    return F.Promise.sequence(yahoo, google, bing).map(timings -> {
        Map<String, Long> map = new HashMap<>();
        map.put("yahoo", yahoo.get(10));
        map.put("google", google.get(10));
        map.put("bing", bing.get(10));
        map.put("total", System.currentTimeMillis() -start);

        return ok(Json.toJson(map));
    });
}

                        

Agenda

  • Getting Started
  • Development Process
  • Error Handling
  • Database
  • Reactive Architecture (Non-Blocking IO)
  • Web Services
  • Real-time Communications
    • Server-Sent Events
    • Websockets
  • Community

WS

Play contains the play.libs.ws.WS library for interfacing with web services

JSON parsing provided through the Jackson library


public static Result sayHello() {
    ObjectNode result = Json.newObject();
    result.put("exampleField1", "foobar");
    result.put("exampleField2", "Hello world!");
    return ok(result);
}

public static Result sayHello() {
    JsonNode json = request().body().asJson();
    if(json == null) {
        return badRequest("Expecting Json data");
    } else {
        String name = json.findPath("name").textValue();
        if(name == null) {
            return badRequest("Missing parameter [name]");
        } else {
            return ok("Hello " + name);
        }
    }
}
                            

...and Scala?

JSON processing provided by the Jerkson library


WS.url(s"https://api.twitter.com/1.1/search/tweets.json").
    sign(signature).
    withQueryString(
        "q" -> query,
        "count" -> 20.toString,
        "result_type" -> "mixed",
        "lang" -> "en").
    get.map { response =>
        val json = Json.parse(response.body)
        val tweets = (json \ "statuses").as[JsArray].value.
        map(simplifyResponse)

        Ok(views.html.twittersearch.render(query,
            tweets.map(tweet =>
            s"${tweet \ "tweet"} - ${tweet \ "name"}")))
    }
                        

Agenda

  • Getting Started
  • Development Process
  • Error Handling
  • Database
  • Reactive Architecture (Non-Blocking IO)
  • Web Services
  • Real-time Communications
    • Server-Sent Events
    • Websockets
  • Community

Server-Sent Events (SSE)

  • One-way streaming communicatoins (server => client)
  • Uses standard HTTP (Content-Type: text/event-stream)
  • Efficient and easy to debug
  • Not supported natively on all browsers (thanks, IE!)

Iteratees

  • Basis of Play's Non-Blocking IO (for now)
  • Enumerator => Enumeratee(s) => Iteratee
  • Play makes it pretty easy
  • Only supported in Scala

SSE


val (stream, channel) = Concurrent.broadcast[JsValue]

WS.url(s"https://stream.twitter.com/1.1/statuses/filter.json").
    sign(signature).
    withQueryString("track" -> URLDecoder.decode(query, "UTF-8")).
    postAndRetrieveStream("")(_ => Iteratee.foreach[Array[Byte]] {
        data => channel.push(Json.parse(data))
    };

Ok.feed(stream &>
    Concurrent.buffer(100) &>
    Enumeratee.map[JsValue](simplifyResponse) &>
    EventSource()).as("text/event-stream")
                        

WebSockets

  • Socket communications over the original HTTP connection
  • Low-level -- not HTTP (define your own protocol)
  • High-frequency two-way communications between client and server
  • Can be implemented with Iteratees or Akka Actors

WebSockets (using Iteratees)


def filter(query: String) = WebSocket.using[JsValue] { request =>
    auth.map { signature =>
    val (stream, channel) = Concurrent.broadcast[JsValue]
    var chunkCache = ""

    WS.url(s"https://stream.twitter.com/1.1/statuses/filter.json").
        sign(signature).
        withQueryString("track" -> URLDecoder.decode(query, "UTF-8")).
        postAndRetrieveStream("")(_ => Iteratee.foreach[Array[Byte]] { data => channel.push(Json.parse(data)) })

    val in = Iteratee.ignore[JsValue]

    val out = stream &>
        Concurrent.buffer(100) &>
        Enumeratee.take(100) &>
        Enumeratee.map[JsValue](simplifyResponse)

    (in, out)
} getOrElse {
    val response: JsValue = Json.obj("error" ->
    "Please configure twitter authentication using the twitter.conf file")
    (Iteratee.ignore[JsValue], Enumerator(response) >>> Enumerator.eof)
    }
}
                        

Agenda

  • Getting Started
  • Development Process
  • Error Handling
  • Database
  • Reactive Architecture (Non-Blocking IO)
  • Web Services
  • Real-time Communications
    • Server-Sent Events
    • Websockets
  • Community

Community

What's next?

  • Play 2.4
    • Improved deployment
    • Experimental support for Reactive Streams
    • Switch from Anorm to Slick for default Scala DB
    • Switch from EBean to JPA for default Java DB
  • Play 3.0
    • Require Java 8
    • Reactive Streams replace Iteratees as the base IO
    • Improve Java APIs for async request/reply handling
    • ..and more!