Play Framework

De webapplicaties van tegenwoordig lijken niet meer op die van een paar jaar geleden. Webapplicaties zijn zo rijk geworden, dat ze nog nauwelijks te onderscheiden zijn van desktop applicaties en deze trend zet steeds verder door. Denk maar aan de chromebooks, waarbij alle applicaties op het web draaien. Applicaties worden uitgebreider en gebruikers stellen steeds hogere eisen aan applicaties. Wanneer een applicatie niet snel genoeg werkt, dan haken gebruikers al snel af. Door het vele gebruik van sociale media kan een applicatie snel gehypet worden, waardoor plotseling veel mensen naar de applicatie gaan. Dit is een mooie mogelijkheid om mensen te binden, maar het kan er ook voor zorgen dat de applicatie de vraag niet aankan, traag wordt en mensen er vervolgens nooit meer naar omkijken.

Naast de verandering in verwachtingen bij gebruikers, is ook op hardwaregebied iets veranderd. Tot 2003 hadden we in de computerindustrie veel plezier van Moore’s Law[1], die stelt dat elke twee jaar het aantal transistors op een chip verdubbelt. Tot 2003 betekende dit dat de kloksnelheid van de CPU steeds hoger werd, waardoor onze applicaties automatisch sneller gingen werken. Deze trend is echter gebroken en hoewel de wet van Moore nog steeds opgaat, zien we dat de kloksnelheid niet meer toeneemt, maar dat we steeds meer cores in onze machines krijgen. Dit betekent dat wij anders moeten gaan programmeren om deze cores goed te benutten.

 


Intel CPU Trends

Reactive Programming
Het reactive manifesto[2] geeft antwoord op de vraag hoe applicaties gebouwd moeten worden die aan de hoge verwachtingen van gebruikers voldoen. Ook gaat het in op de keuzes die we moeten maken om de hardware zo volledig mogelijk te gebruiken. Gesteld wordt dat applicaties moeten reageren op de gebruiker, op fouten, op load en op events. De Akka toolkit is hier met het actor model een goede aansluiting op. Het Play Framework is geschreven in Scala bovenop de Akka toolkit. Het integreert dan ook naadloos met Akka. Dit maakt Play uitermate geschikt om reactive webapplicaties te maken. Dit artikel geeft een introductie van Play. Binnen Play kun je zowel Java als Scala gebruiken. Scala sluit echter het beste aan bij de functionele aspecten in Play en zorgt ervoor dat de hoeveelheid boilerplate minimaal is. In de code voorbeelden van dit artikel gebruiken we daarom Scala.

Waarom Play
Het Play Framework is een hedendaags framework waarmee je reactive applicaties kunt bouwen. Het heeft een aantal krachtige features. Als je code aanpast, kun je in je browser het scherm verversen om het resultaat te zien, zonder dat je daarvoor de server moet herstarten of je applicatie opnieuw moet deployen. Daarnaast is het volledig typesafe, waardoor de compiler veel controles kan uitvoeren en daarmee mogelijke fouten kan voorkomen. In de routing van requests zie je duidelijk de HTTP methodes terug en je kunt hierdoor eenvoudig een volledige RESTful applicatie bouwen. Play biedt geen mogelijkheid om server-side een sessie bij te houden. Hierdoor is elke applicatie volledig stateless en kun je eenvoudig meerdere instanties naast elkaar draaien, zonder dat je moeilijke trucs uit moet halen op tussenliggende proxies zoals sticky session. Tevens kun je gewoon doorwerken in situaties waarin meerdere instanties zijn en hiervan eentje crasht. Je volgende request wordt dan gewoon verwerkt door de instantie die nog wel werkt. Deze kenmerken van Play zorgen ervoor dat het een fijn web framework is om mee te werken.

Aan de slag
De snelste manier om met Play (of andere componenten van het Typesafe platform) aan de slag te gaan is door Typesafe Activator[3] te gebruiken. De activator biedt op moment van schrijven 99 templates. Je kunt een template kiezen en op basis van die template een project starten. Bij elke template zit een uitgebreide beschrijving en werkende code die je in je bestaande IDE of in de browser kunt aanpassen, testen en draaien. Het is sterk aan te raden om de diverse templates in de activator eens te bekijken.

In de volgende paragrafen zullen de belangrijkste componenten van Play voorbij komen. Dit zijn achtereenvolgens de Controllers, Routing, Asynchroniteit en de Templating. Tot slot wordt er uitgelegd hoe het testen in zijn werk gaat.


Typesafe Activator

 

Controllers
Controllers zijn objects die de Controller trait overerven. In de controller worden Actions gedefinieerd die uitgevoerd kunnen worden. Een action is een functie van Request => Result. Om het makkelijk te maken deze actions te schrijven, biedt Play de Action companion object. Deze heeft een aantal helpers om de functie te schrijven en zorgt ervoor dat de juiste content-type wordt gezet bij het antwoord. De meest simpele action is degene waarbij je niet naar het request kijkt, maar direct een resultaat teruggeeft (zie simpel in Listing 1). De meer uitgebreide versie accepteert een content parser en een parameter. De content parser zet de request body om naar het vereiste formaat en geeft een foutmelding (400: Bad Request) terug aan de client als het formaat niet in orde is. De functie parameter komt via de URL binnen en wordt uitgelegd in de paragraaf routing.


object Example extends Controller {
def simpel = Action {
Ok("Dit is een simpele actie")
}
def reservation(id: Long) = Action(parse.xml) { request =>
Ok(<thanks><id>{id}</id><name>{(request.body \ "name").text}</name></thanks>)
}
}

Listing 1

Routing
Het doel van de router is om de HTTP requests die naar de play applicaties gestuurd worden door te zetten naar de juiste action. Bij het identificeren van een HTTP requests zijn twee elementen van belang:

  • De URL
  • De methode: GET/PUT/POST/DELETE

De actions die in de vorige paragraaf gemaakt zijn, moeten een plek krijgen in het routing bestand. Dit bestand is in het project te vinden op de volgende locatie: ‘conf/routes’. De twee actions in de controller krijgen ieder een route definitie (zie listing 2). In een routing kan verwezen worden naar variabele elementen in de url (zoals hier gedaan is met de ‘id’) en naar elementen in de query string. Staat de id niet in de URL, dan wordt de id gezocht in de query string. Als een parameter wordt verwacht, maar deze niet kan worden gevonden, dan geeft Play een duidelijke melding (zie afbeelding 3) met de juiste http status code (400).


GET     /simpel                     controllers.Example.simpel
PUT /reservation/:id controllers.Example.reservation(id: Long)

Listing 2


Afbeelding 3

Asynchroon
Het Play Framework is gebouwd bovenop Akka. Akka is een toolkit om concurrent, gedistribueerde en fout-tolerante-event-gedreven applicaties te maken (je kunt in dit magazine meer lezen over Akka). Dat betekent dat de kern volledig asynchroon is. Je kunt ook vanuit Play direct gebruik maken van Akka. Hierdoor kun je applicaties maken die robuust zijn en snel reageren. Tot nu toe hebben we twee acties gezien die direct een resultaat opleverden. Meestal zit er meer logica (of IO) in een Action en het is niet wenselijk dat de huidige thread hierbij wacht (blokkeert) op resultaat. Door gebruik te maken van een Future[Result] wordt het werk niet op de huidige thread uitgevoerd. De Future[Result] levert bij completion een Result op. Play zorgt ervoor dat het Result uit de future afgehandeld wordt zoals een normale Result. Hoewel de client blijft wachten tot het resultaat opgeleverd wordt, zijn op de server geen threads die geblokkeerd zijn. Door ervoor te zorgen dat geen threads blokkeren, kan optimaal gebruik gemaakt worden van de resources in het systeem en kunnen deze bijvoorbeeld gebruikt worden om andere requests af te handelen.

In het volgende voorbeeld gaan we gegevens ophalen over het weerbericht in de opgegeven stad (zie Listing 3). Dit is een operatie waarbij een call gedaan wordt naar een webservice. Dit is typisch een actie waarbij je niet wilt dat in je systeem threads staan te wachten tot de webservice resultaat oplevert. De WS library van Play maakt het eenvoudig om een webservice aan te roepen en maakt gebruik van non-blocking IO. Allereerst construeren we een url die we aanroepen middels “WS.url(url).get()”. Het resultaat van deze functie is een Future[Response]. Het Response object biedt ons de mogelijkheid om de content als json op te halen. De map functie van een Future neemt als parameter een functie welke de huidige type omzet naar een ander type. We kunnen hiermee dus een functie schrijven die werkt op het Response object en deze gebruikt om een Result te maken. Dit gebeurt allemaal zonder te blokken, dus na het toepassen van de functie Response => Result via map is het resultaat Future[Result]. Deze kan dan als resultaat opgeleverd worden aan Play die het naar de gebruiker rendert.


def temperature(city: String) = Action.async {
val url = s"http://api.openweathermap.org/data/2.5/weather?q=$city,NL&mode=json&units=metric"
WS.url(url).get().map { response =>
val temp = (response.json \ "main" \ "temp").as[Double] val rain = (response.json \ "rain" \ "3h").asOpt[Double] Ok(<html><body>
<h1>Het is nu {temp}&deg; C in {city}</h1>
Er komt {rain.fold("geen")(mm => s"${mm}mm")} regen
</body></html>).as(HTML)
}
}

Listing 3

Templating
In de vorige paragraaf hebben we een HTML pagina opgeleverd waarop het weerbericht getoond wordt. Het is echter niet wenselijk om je HTML pagina’s in je controller te definiëren. Om pagina’s makkelijk te definiëren, biedt Play een templating mechanisme aan dat gebaseerd is op Scala. Dit betekent dat de templates volledig statisch getypeerd zijn, waardoor de compiler de code goed kan analyseren en een goede foutmelding geeft als iets mis is. Op de eerste regel van een template staat de function signature. Als een template gecompileerd wordt, dan wordt een Scala object gemaakt met een apply functie die de gegeven parameter definitie heeft. Deze apply functie kan aangeroepen worden zoals elke ander functie.


@(city: String, temp: Double, rain: Option[Double])
<html>
<head>
<title>Het weer in @city</title>
</head>
<body>
<h1>Het is nu @temp&deg; C in @city</h1>
@if(rain.isDefined) {
Er komt @(rain.get)mm regen
} else {
Het blijft droog!
}
</body>
</html>

Listing 4

 


def temperatureTmpl(city: String) = Action.async {
val url = s"http://api.openweathermap.org/data/2.5/weather?q=$city,NL&mode=json&units=metric"
WS.url(url).get().map { response =>
val temp = (response.json \ "main" \ "temp").as[Double] val rain = (response.json \ "rain" \ "3h").asOpt[Double] Ok(views.html.weer(city, temp, rain))
}
}

 

Listing 5

Doordat templates in feite functies zijn, kun je composities maken. Je kunt in je template een andere template aanroepen, eventueel met parameters. In onderstaande code maken we een snippet om afhankelijk van de hoeveelheid regen een plaatje te tonen. Deze kunnen we met ‘@weer_image(rain)’ aanroepen. Dit mechanisme is eenvoudig en doordat het functies zijn, is het enorm krachtig.

<bestand: weer_image.scala.html>


@(rain: Option[Double])
@if(rain.getOrElse(0.0) > 1.0) {
<img src="http://bit.ly/1hCDcPV" width="50px"/>
} else {
<img src="http://bit.ly/1kpGWDH" width="50px"/>
}

Listing 6

Testen
Bij elke applicatie is het van belang om deze goed te testen. In Play zijn de actions en templates functies die input krijgen en output genereren. Hierdoor is het eenvoudig om ze te testen. De standaard testmethodiek in Play applicaties is de Specs2 library. In dit artikel zullen we dan ook Spec2 gebruiken in de voorbeeldcode. Het is overigens geen probleem om een ander test framework te gebruiken. Als je ervoor zorgt dat je de juiste libraries toevoegt aan je build file, dan kun je bijvoorbeeld je tests in Scalatest schrijven.

Er bestaan diverse helper functies om je te helpen bij het maken van tests. Zo verwacht een action in een controller een request. Het FakeRequest object biedt functies om een request object te creëren die je kunt gebruiken om de action aan te roepen. Zie hieronder een voorbeeld van een test waarbij de reservation action aangeroepen wordt met het request. Vervolgens wordt het resultaat gevalideerd.


class ExampleControllerSpec extends PlaySpecification with XmlMatchers{
"In ExampleController the reservation" should {
"return xml body" in {
val req = FakeRequest().withBody(<reservation><name>Jeroen</name></reservation>)
val reservationId = 123
val result = Example.reservation(reservationId)(req)
status(result) must equalTo(OK)
contentType(result) must beSome(MimeTypes.XML)
val xml = XML.loadString(contentAsString(result))
xml must \("id") \> "123"
xml must \("name") \> "Jeroen"
}
}
}

Listing 7

Aangezien templates ook functies zijn, zijn deze op vergelijkbare wijze te testen. In onderstaande test wordt de logica in de view getest. Als er meer dan 1mm neerslag verwacht wordt, dan moet het regenwolkje getoond worden.


"show rain cloud image" in {
val html = views.html.weer_image(Some(1.1d))
contentAsString(html) must contain("http://bit.ly/1hCDcPV")
contentAsString(html) must not contain("http://bit.ly/1kpGWDH")
}

Listing 8

Je kunt ook integratie tests uitvoeren door je applicatie te starten en met Selenium de schermen testen. Hiervoor biedt Play de WithBrowser trait waarbij je gemakkelijk Selenium tests kunt schrijven.


"show an image" in new WithBrowser {
browser.goTo("/temptmpl/Bussum")
browser.title must equalTo("Het weer in Bussum")
browser.$("img").getAttribute("src") must contain("http://bit.ly/")
}

Listing 9

Model
Het Play framework dwingt geen persistentie methode af. Je bent dus volledig vrij om je model te maken op welke manier je wilt. Play biedt wel wat hulp bij het communiceren met een relationele database middels een JDBC connection pool. Zie de documentatie van Play voor meer informatie over dit onderwerp[4].

Conclusie
Ik ben onder de indruk van de functionaliteit die het Play Framework mij biedt bij het bouwen van webapplicaties. Daarnaast is de naadloze integratie met Akka -waardoor je zeer robuuste en schaalbare applicaties kunt maken- zeer krachtig. De hele Typesafe stack biedt veel nieuwe mogelijkheden bij het ontwikkelen van applicaties die altijd en snel moeten werken. Ik verwacht een enorme vlucht in het gebruik van de Typesafe stack en het ontwikkelen van reactive applications.

De code van dit artikel is te vinden op github[5].

 

Referenties
[1] http://en.wikipedia.org/wiki/Moore%27s_law

[2] http://www.reactivemanifesto.org

[3] https://typesafe.com/platform/getstarted

[4] https://typesafe.com/platform/runtime/playframework

[5] https://github.com/jgordijn/play