PLAY FRAMEWORK
Webnesday #5
Mirko Stocker
Institut für Software
Rapperswil, 13. Mai, 2015
Mirko Stocker
InfoQ Editor
Wissenschaftlicher Mitarbeiter
Application Architecture
Scala, Ruby
Cloud Solutions
www.infoq.com/author/Mirko-Stocker
Parallele Programmierung
Web-Entwicklung
User Interfaces
www.burg-au.ch
appquest.hsr.ch
www.free-enterprise.ch
Product Manager
Consulting und Projekt-Begleitung
Eclipse Plug-ins für C++/Scala
Forschungsprojekte
github.com/misto | misto.ch | @m_st | facebook.com/misto | ch.linkedin.com/in/misto
Seite 2
© Mirko Stocker 2014
Play Framework
Seite 3
© Mirko Stocker 2014
Warum Play?
JVM, aber kein JEE/Servlet
HTTP Interface, non-blocking I/O
Typsicherheit: Templates, URLs
Entwicklungszyklus: Edit -> Refresh
Seite 4
© Mirko Stocker 2014
Grundlagen des Webs
HTTP
Back
Reload
URLs
Seite 5
© Mirko Stocker 2014
Grundlagen des Webs – Bezogen auf Play
HTTP nicht verstecken
Stateless
Reload
URLs sprechen
Stateless: Der Back-Button funktioniert
HTTP ist nicht nur ein Transport für RPC (REST Maturity Model)
URLs sollten sprechen (Level 1, Links zu verschiedenen Ressourcen)
Reload: Als Entwickler will ich Änderungen sofort sehen
Seite 6
© Mirko Stocker 2014
Play ist Stateless
Kein Zustand im Applikations Tier
Im Unterschied zu Java Servlet HttpSession
Zustand ist also eine Kombination aus
Client Session State
Database Session State
Vorteile
Einfach horizontal skalierbar, da egal ist, welche Instanz einen Request
behandelt (Cloud-Ready)
Kein Synchronisationsaufwand in der Applikation
Entwicklung und Test vereinfacht, da Verhalten reproduzierbar ist
Seite 8
© Mirko Stocker 2014
Play macht dem Entwickler Spass
Play kommt ohne Applikationsserver aus
Basiert auf Netty
Kann aber auch als WAR deployed werden
Kein Deployment während der Entwicklung
Beim Reload werden die geänderten Klassen und Templates neu kompiliert
und geladen
Gute Fehlermeldungen direkt im Browser
Gute Unterstützung von Unit, Functional und Integration Testing
Sehr guter JSON und JavaScript Support
JSON Parsen, Manipulieren und Generieren
Webjars Support und Require-JS eingebaut
Minimization, LESS/CoffeeScript Compilation, etc.
Seite 9
© Mirko Stocker 2014
Play macht dem Entwickler Spass
Seite 10
© Mirko Stocker 2014
Play Komponenten
Integrierter Webserver (Netty)
Webservices
API
HTTP Interface
Template
Engine
Asset
Compilation
Async I/O
HTML Form
Validation
JSON
Akka Actors
Diverse (NO-) SQL Anbindungen
Seite 11
© Mirko Stocker 2014
Interaktives
Buildsystem
und
Projekttemplates
Play User
Seite 12
© Olaf Zimmermann, Mirko Stocker 2014
Neues Play Projekt Erstellen
Play wird von Typesafe (Scala, Akka) entwickelt
Neue Play Projekte erstellt man am einfachsten mit dem Activator
Activator: Build-Tool und Projekt-Generator von Typesafe
Enthält diverse Beispielprojekte und Templates
Seite 13
© Mirko Stocker 2014
Neues Play Projekt Erstellen
Verfügbare Templates auflisten
./activator
list-templates
Neues Projekt aus Template play-java erstellen
./activator
new
my-play-java
play-java
Browser-basiertes UI starten
./activator ui
Interaktives CLI starten
./activator
Im Projektordner
Eclipse Projekt erstellen
./activator eclipse
IntelliJ IDEA Projekt erstellen
./activator idea
Seite 14
© Mirko Stocker 2014
Projektlayout
Sourcecode
Buildfile
Static Assets
Tests
├──
├──
│
│
│
│
│
├──
├──
│
│
├──
│
├──
│
│
│
│
│
│
└──
activator
app
├── controllers
│
└── Application.java
└── views
├── index.scala.html
└── main.scala.html
build.sbt
conf
├── application.conf
└── routes
logs
└── application.log
public
├── images
│
└── favicon.png
├── javascripts
│
└── hello.js
└── stylesheets
└── main.css
test
├── ApplicationTest.java
└── IntegrationTest.java
Seite 15
© Mirko Stocker 2014
MVC in Play
MVC Pattern lässt sich aus der Projektstruktur erahnen:
├──
│
│
│
│
│
Model
app
├── controllers
│
└── Application.java
└── views
├── index.scala.html
└── main.scala.html
Controller
View
Models sind typischerweise im app/models Ordner zu finden
Seite 16
© Mirko Stocker 2014
URLs und Routing in Play
Sprechende URLs sehen nicht nur schöner aus
sie sind auch stabiler
wir können sie Bookmarken, Twittern
und als Entwickler auch direkt manipulieren
URLs in Play sind first class Citizens (und nicht bloss Strings)
Compiler prüft Verwendung
Interne tote Links sind nicht möglich
Alle URLs werden in conf/routes zentral deklariert
Klasse.Methode
GET
GET
GET
POST
/
/computers
/computers/new
/computers
controllers.Application.index
controllers.Application.list
controllers.Application.create
controllers.Application.save
GET
POST
/computers/:id
/computers/:id
controllers.Application.edit(id:Long)
controllers.Application.update(id:Long)
Seite 17
© Mirko Stocker 2014
Controller: Actions
Ein (Page) Controller behandelt die eingehenden Requests
Controller ist eine Sammlung von Actions
package controllers;
import play.*;
import play.mvc.*;
public class Application extends Controller {
public static Result index() {
return ok("It works!");
}
}
% GET -s 'http://localhost:9000'
200 OK
It works!%
Seite 18
© Mirko Stocker 2014
Controller: Actions
Der Play Router mappt die URLs zu den Controller Methoden
GET
/
controllers.Application.index()
GET
/assets/*file
controllers.Assets.at(path="/public", file)
Vordefinierte Hilfsmethoden für gängige HTTP Status Codes
Beispielsweise auch für Redirects
public static Result index() {
return redirect(routes.Application.index());
}
% GET -e 'http://localhost:9000'
303 See Other
Location: /
Seite 19
© Mirko Stocker 2014
Controller: Routing
Verschiedenste URL Pattern möglich
GET
/computers/:id
controllers.App.edit(id: Long)
GET
/computers/search
controllers.App.search(query: String)
GET
/computers
controllers.App.list(page: Int ?= 1)
GET
/computers
controllers.App.show(page: Page)
Eigene
Klasse
Entsprechen Methoden-Parameter und müssen nicht selber geparsed
werden (sogar mit eigenen Klassen):
public static Result edit(long id) { … }
public static Result search(String query) { … }
public static Result list(int page) { … }
public static Result show(Page page) { … }
Seite 20
© Mirko Stocker 2014
Controller: Routing prüft Parameter
% GET -e 'http://localhost:9000/computers/blabla'
400 Bad Request
…
Seite 21
© Mirko Stocker 2014
JSON im Request Body
@BodyParser.Of(BodyParser.Json.class)
public static Result sayHello() {
JsonNode json = request().body().asJson();
String name = json.findPath("name").textValue();
if(name == null) {
return badRequest("Missing parameter [name]");
} else {
return ok("Hello " + name);
}
}
Jackson
% curl
--header "Content-type: application/json" --request POST
--data '{"name": "HSR"}' http://localhost:9000/sayHello
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: 9
Hello HSR
Seite 28
© Mirko Stocker 2014
JSON im Request Body – Scala
case class Named(name: String)
def sayHello() = Action(parse.json[Named]) { request =>
val named = request.body // named hat Typ Named
Ok(s"Hello ${named.name}")
}
% curl
--header "Content-type: application/json" --request POST
--data '{"name": "HSR"}' http://localhost:9000/sayHello
Bad Request wenn fehlt
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: 9
Generiert JSON Parser
Hello HSR
Seite 29
© Mirko Stocker 2014
JSON als Result
JSON erstellen
public static Result sayHello() {
ObjectNode result = Json.newObject();
result.put("message", "Hello Webnesday!");
return ok(result);
}
ok() ist überladen und setzt automatisch den richtigen Content-Type
% curl --request GET http://localhost:9000/sayHello
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"message":"Hello Webnesday!"}
Seite 30
© Mirko Stocker 2014
Models
Typischerweise POJOs in app/models
Play ist nicht auf einen spezifischen OR-Mapper festgelegt
JPA, JDBC, etc.
Auf der Scala Seite auch Anorm und Slick (ähnlich wie MS LINQ)
Beispiel mit JPA:
@Transactional
public static Result list() {
EntityManager em = play.db.jpa.JPA.em();
TypedQuery<Computer> query = em.createQuery(…)
Collection<Computers> computers = query.getResultList();
return ok(views.html.Application.index.render(user, computers));
}
Seite 31
© Mirko Stocker 2014
Non-Blocking I/O
Seite 32
© Olaf Zimmermann, Mirko Stocker 2014
Das Problem mit Threads
Threads sind teuer in der Erstellung
Lösung: Wiederverwenden, Threadpool verwenden
Wie gross soll der Threadpool sein?
Aus CPU-Sicht: Ein Thread / Core
Zu gross
Memory-Overhead
Context-Switching
Zu klein
Abhängigkeit von Downstream-Latenz
Requests blockieren wenn keine Threads mehr vorhanden sind
Lösung:
Keine Threads blockieren! Ein Thread / Core dauernd beschäftigt
Non-Blocking I/O (NIO in Java, select / poll / epoll)
Seite 33
© Olaf Zimmermann, Mirko Stocker 2014
Latenz in einem verteilten System mit Blocking
DB
Backend
Service
Web
Server
Load
Balancer
Web
Server
S3
DB
Backend
Service
DB
Backend
Service
3rd Party
Service
Seite 34
© Olaf Zimmermann, Mirko Stocker 2014
DB Latenz
geht hoch
Latenz in einem verteilten System mit Blocking
Threads werden «gestaut»
Backend
Service
Web
Server
Load
Balancer
Web
Server
DB
S3
DB
Backend
Service
DB
Backend
Service
3rd Party
Service
Seite 35
© Olaf Zimmermann, Mirko Stocker 2014
Evented vs. Threaded Web Server
Play basiert auf Netty, einem evented Web Server
Traditionell sind Web Server eher Thread based: pro Request ein Thread
Event basierend: Pro CPU ein Thread, Threads werden nicht blockiert.
Auch asynchron oder non-blocking genannt
Siehe auch Reactor Pattern (Pattern Oriented Software Architecture 2)
Sobald bei der Bearbeitung des Requests auf eine downstream
Datenquelle (WS, DB) gewartet werden muss, wird der Thread freigeben
und ein Callback registriert
Event-based skaliert besser bei vielen Requests
Thread ist nur besetzt, wenn es etwas zu tun gibt
Unabhängigkeit von der Downstream Latenz
Seite 36
© Mirko Stocker 2014
Non-Blocking Web Services
Controller Action in Play kann auch ein Promise<Result> zurückgeben
Promise: analog zu Future (Proxy Objekt für ein zukünftiges Resultat)
Aufruf der Action beendet so schnell wie möglich
Thread wird für nächsten Request frei
Resultat wird ausgeliefert, wenn die Berechnung fertig ist
public static Promise<Result> longRunningAction() {
System.out.println("Action entry");
String url = "http://a.really.slow.server";
}
Promise<Result> result = WS.url(url).get().map(response -> {
System.out.println("Processing result");
return ok(response.getBody()).as("text/html");
});
Action entry
System.out.println("Action exit");
Action exit
return result;
Processing result
Seite 37
© Mirko Stocker 2014
Play Zusammenfassung
Play ist eine leichtgewichtige moderne Alternative für Java Web
Stateless, RESTful (Cloud-Ready)
Embrace HTTP, nicht verstecken und wegabstrahieren
Schneller Entwicklungszyklus, kein langwieriges Deployment
Non-blocking und evented, läuft auf Netty
Intern in Scala geschrieben, mit Java und Scala API
Typsicher: Routing, URLs und Templates
LESS, CoffeeScript, React, etc. unterstützt im Buildsystem
JSON, Websockets, SSE Support
Seite 38
© Mirko Stocker 2014
Danke für eure Aufmerksamkeit!
Seite 39
© Olaf Zimmermann, Mirko Stocker 2014