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