Playframework artikel van java magazine

Het playframework is een action based web applicatie framework waarbij snelheid van ontwikkeling voorop staat. DIt wordt gerealiseerd door een eigen classloader systeem die elke aanpassing aan java sources en resources meteen oppikt en opnieuw inlaadt zonder enige tussenkomst van de ontwikkelaar. Dit betekent dat er bijna geen re-starts van de server meer nodig zijn. Het playframework zal zelf geen data op de server vasthouden zodat applicaties in principe onbeperkt schaalbaar zijn. Elke actie die op de applicatie uitgevoerd kan worden, kan aan dynamische urls verbonden worden, zodat er nette REST urls gevormd kunnen worden. Het playframework kan complexe data structuren opbouwen aan de hand van http parameters door middel van data binding zonder dat je daar zelf veel voor hoeft te doen. Kortom het playframework maakt het bouwen van webapplicaties weer een stuk leuker.

Dit artikel wil je een globaal overzicht geven wat er allemaal mogelijk is en hoe simpel het is om er mee te werken. Voor een uitgebreider voorbeeld is de documentatie op de website zelf ook erg goed.

MVC
Het playframework is een MVC systeem waarbij op elke laag (dus zowel op de Model, als de View als de Controller) aanpassingen zijn doorgevoerd die het de ontwikkelaars makkelijker maken om web applicaties te bouwen. Om een nieuwe applicatie te gaan bouwen is het voldoende om “play create” in te vullen. Hierna zal play de default structuur van de applicatie bouwen. Via “play run” of “play test” kan de server gestart worden om de applicatie te draaien en om de tests te draaien.

Voor je iets met de database kunt doen dien je deze wel eerst te enablen. Dat kun je doen door in de conf directory het bestand application.conf te openen. Hierin worden allerlei algemene instellingen bewaard waaronder de database. Zoek hierin de commentaar regel # db = men en verwijder de # zodat de in memory database wordt gebruikt.

Model
Indien het Model van playframework wordt ge-extend dan heb je active records tot je beschikking. Dit houdt in dat je voor elk model meteen een aantal standaard CRUD acties tot je beschikking hebt, zoals save, update, delete, find, count. Het model kan alle standaard JPA annotaties bevatten die door hibernate uitgevoerd zullen worden. De sessie wordt door middel van een playframework plugin geopend op elke request (en ook binnen scheduler jobs en bij het uitvoeren van tests). Dit gebeurt door middel van bytecode enhancement die door de playframework plugin uitgevoerd wordt.

Een onconventionele aanpak voor de modellen is dat de properties public zijn. Elke java developer zal bij het lezen van deze regel wel even achter zijn oren krabben, omdat daarmee het encapsulatie principe van OO programeren overboord wordt gezet, maar als je er even over nadenkt is dit weer niet zo erg. Standaard POJO’s bestaan uit een aantal private properties die allemaal een public getter en setter hebben, waarmee de properties dus eigenlijk weer public zijn. Door deze properties nu meteen public te maken scheelt dat een boel onnodige code. Onder water maakt het playframework wel gebruik van getters en setters. Deze worden ook door middel van bytecode enhancement toegevoegd als deze niet bestaan. Als je dus niet wilt dat een property aangepast kan worden kun je dus zelf een getter methode opnemen die het veranderen van de property niet zal toestaan.

Een voorbeeld van een model voor een Boek:

@Entity
public class Book extends Model {
  @Required
  public String name;
  @Required
  public String author;
  @Required
  public String isbn;
  public Date publishedAt;

  public static Book findBook(String name, String author) {
     return Book.find("byNameAndAuthor", name, author).first();
  }
}

Deze class vertegenwoordigt een book met wat properties. Er zijn geen getters en setters aanwezig, dus de class blijft klein. Doordat de book class het play model extend, wordt er automatisch een id toegevoegd van het type long en zijn de active record methodes beschikbaar.

Het findBook commando gebruikt een find commando van het playframework. Hierin kan een standaard JPA query worden opgenomen maar play kan deze query ook zelf interpreteren, zoals hier gebeurd is door het keyword “byNameAndAuthor” op te geven. Hierdoor zal er een JPA query gedefinieerd worden waarbij de name en author ingevuld worden. Ook dat maakt de code weer wat leesbaarder en ook nog korter. De @Required en andere validatie annotaties worden door play uitgevoerd op het moment dat de validaties aangeroepen worden. Dit kan o.a. door book.validateAndCreate() aan te roepen, of door de controller een @Valid notatite te geven maar daarover later meer.

Views
De standaard template engine die gebruikt wordt is gebaseerd op groovy en kent al een aantal standaard tags die je kunt gebruiken om if-else en loop structuren te maken. Als je zelf tags wilt maken of wilt uitbreiden is dit ook eenvoudig zelf te realiseren. Dit kan door een java class te extenden of door groovy snippets te maken. De templates worden vervolgens gerendered en aan de gebruiker getoond. Dit kan ook gebruikt worden om emails op te stellen. Dezelfde template die de webpagina kan tonen, kan dan verstuurd worden als email. De standaard template kan vervangen worden door een java of zelfs een scala variant. Bij de java variant worden de templates gegeereerd wat een performance winst op levert die tot 10x zo snel kan zijn als de groovy variant. Als de scala variant gebruikt wordt, kan zelfs een compiler type check afgedwongen worden voor de templates, wat bij de groovy variant niet kan.

Controllers
De controllers zorgen o.a. voor het binden van de http parameters naar objecten. Een hele simpele flow voor book management kan er zo uitzien:

  • Een binnenkomst pagina waarbij max 10 books getoond worden
  • Elke klik op de naam van een book zal de details van een book tonen
  • Een klik op een edit button zal de pagina in edit mode tonen
  • Waarbij de save het book zal opslaan.

Een binnenkomst pagina waarbij alle (max 10) books getoond worden, is te vinden op localhost:9000/books/index. Books is de naam van de controller en index is de naam van de actie binnen deze controller. Deze methode ziet er als volgt uit

public static void index() {
 List books = Book.all().fetch(10);
 render(books);
}

De template zal default opgezocht worden in de app/views/ControllerName directory met de naam van de actie die uitgevoerd is als filename. Doordat de controller een render actie doet met alleen books als parameter, wordt de inhoud van de books als parameter met de naam books toegevoegd aan de context die door de view template uitgevraagd kan worden. Dit is tevens de reden dat de list met books expliciet benoemd wordt, aangezien anders het playframework niet weet onder welke naam de parameters bewaard hadden moeten worden. Dit kan je wel op andere manieren oplossen binnen het playframework, maar als ik alle opties van het playframework ga benoemen, dan wordt dit een serie artikelen in plaats van 1 enkel artikel.

De template:

#{extends 'main.html' /}
<ul>
#{list items:books, as:'book'}
 <li>#{a @Books.show(book.id)}${book.name}#{/a}
#{/list}
</ul>
#{a @Books.add()}Add#{/a}

De template begint met een extend statement. Dit houdt in dat er een main.html pagina is die deze pagina extend. Deze pagina heeft een tag in zich die doLayout heet, welke de inhoud van deze template zal toevoegen. Dit is dus een decorator zoals tiles of sitemesh.

Vervolgens loopt de template over de books en toont alle books in een li tag. Hierin staan een aantal ander play tags die veel gebruikt zullen worden. De #{a} tag creeert een a href, waarbij de clickable content de naam van het book is. Wat opvalt binnen de #{a} tag is nog de @Books.show(book.id) notatie. Dit is een play notatie die ervoor zorgt dat het href attribuut van de a tag dusdanig wordt opgebouwd dat bij het klikken op de link er een actie wordt gedaan die resulteert in een methode show van de controller Books, waarbij als parameter het id van het book wordt meegegeven. De url ziet er dan als volgt uit: http://localhost:9000/books/show?id=1. Deze url wordt opgebouwd door in een speciaal bestand, het routes bestand, te zoeken naar de controller met actie. Als de actie niet gevonden wordt, of de pagina bestaat niet dan krijg je hiervan een duidelijke foutmelding in je browser te zien.

De show actie zelf is net zo simpel als het overzicht

public static void show(Long id) {
  Book book = Book.findById(id);
  render(book);
}

De show methode zal vervolgens het book object uit de database halen (dmv een andere static methode die playframework heeft toegevoegd)

De simpele show template:

#{extends 'main.html' /}
<table>
  <tr><td></td><td>${book.name}</td></tr>
  <tr><td></td><td>${book.author}</td></tr>
  <tr><td></td><td>${book.isbn}</td></tr>
  <tr><td></td><td>${book.published?.format('dd-MM-yyyy')}</td></tr>
</table>
#{a @Books.edit(book.id)}#{/a} #{a @Books.index()}#{/a}

De notatie is een notatie voor playframework om messages te gaan inlezen. Uiteraard kunnen voor de messages per taal resourcebundles opgegeven worden. Een voordeel van de playframework manier is dat de key gewoon afgebeeld wordt indien deze niet gevonden wordt. Doordat een groovy template is kun je de groovy null check gebruiken op objecten. Dit zie je in de book.published?.format(‘dd-MM-yyyy’). Dit houdt in dat de format methode alleen uitgevoerd zal worden indien de book.published property niet null is.

Ook hier wordt weer een link opgenomen. Deze moet resulteren in een edit actie. Nu lijkt het of elke link hard gecodeerd staat maar gelukkig is dat niet zo. Om de acties en controllers aan elkaar te praten maakt playframework gebruik van een routes file. In dit bestand kun je de url’s opnemen die moeten resulteren in een bepaalde actie. Voorbeeld:

GET     /show/{id}  Books.show
GET     /add        Books.add
POST    /save/{id}  Books.save
GET     /edit/{id}  Books.edit

Als vervolgens een @Books.show(book.id) gevonden wordt in de template dan wordt de routes file bekeken om te zien welke url daarmee overeen komt. In dit geval dus de /show/ url. Deze wordt vervolgens gezet als de url, en als hierop geklikt wordt zal dezelfde file bekeken worden om te zien welke action dan uitgevoerd dient te worden. De regular expression in de url wordt in de url vervangen door het id veld en als de action methode aangeroepen wordt dient er wel een methode te zijn die die parameter (met precies die naam) heeft.

Template edit.html

#{extends 'main.html' /}
#{form @Books.save()}
<table>
    #{tableField book.name, fieldName:'book.name' /}
    #{tableField book.author, fieldName:'book.author' /}
    #{tableField book.isbn, fieldName:'book.isbn' /}
    <tr><td colspan="2"></td></tr>
</table>
#{/form}
#{a @Books.show(book.id)}#{/a}

#{form} creates the form with the correct url for saving. By adding a hidden book.id parameter and creating a controller method that takes as parameter a book, the playframework will :
– fetch the book from the database for you
– and then copy the filled in values into the book that was retrieved from the database

De tablefield tag is een eigen tag. Deze zal een tr met td maken met daarin een input voor het opgegeven veld. Een eigen tag is een een groovy template waaraan je echter nog parameters mee kunt geven. Dit is hier gedaan als impliciete parameter (zijnde de property van het book zoals de name) en als expliciete parameter, zijnde de fieldName parameter.

Controller voor save:

public static void save(@Valid Book book) {
  if (validation.hasErrors()) {
     params.flash();
     validation.keep();
     edit(book.id);
  }
  book.save();
  index();
}

De @Valid annotatie zorgt er voor dat het book dat opgegeven is alle veld annotaties uitgevoerd worden. In dit geval zijn er alleen required velden dus als er een veld leeg gemaakt wordt, dan zulllen er fouten staan in de validation. Dit wordt dan vervolgens gecontroleerd. Als er fouten zijn geconstateerd dan:
– worden de params en validatie errors bewaard in de cookie
– wordt een redirect naar de edit actie uitgevoerd.
– vervolgens zal het playframework de flow verlaten dus niet meer bij de book.save methode uitkomen.

De parameters die opgegeven zijn worden als cookie bewaard omdat het playframework niets op de server bewaard. De opgegeven waardes worden dan weer aan de gebruiker getoond doordat er een redirect naar de edit wordt uitgevoerd, waarbij de opgegeven waardes uit het cookie gehaald worden en weer als parameters doorgegeven worden. Op deze manier houdt de server geen enkele state vast maar worden de opgegeven waardes toch weer getoond. Een nadeel hiervan is dat een cookie maar max 4k aan tekens mag bevatten. Dit kan dan omzeild worden door geen redirect uit te voeren (dus geen aanroep naar edit(book.id)) maar een render actie uit te voeren (dmv render(“Books/edit.html”, book) waarbij dan de parameters niet geflashed hoeven te worden, aangezien de cookie dan niet gebruikt wordt.

Indien er geen fouten zijn, kan het book bewaard worden en wordt er een redirect naar de list van booken uitgevoerd.

Het enige wat nog ontbreekt is de add methode:

public static void add() {
  Book book = new Book();
  render("Books/edit.html", book);
}

Hier wordt de edit pagina opnieuw gebruikt en wordt er een leeg book object toegevoegd. Vervolgens zal de rest van de flow precies hetzelfde zijn.

Nadeel
Een van de weinige nadelen die ik tot nu toe ben tegengekomen met het playframework is dat er geen ondersteuning is voor Maven. Indien je dus applicaties ontwikkelt binnen een complete mavenized ontwikkelstraat, inclusief deployments, dan zal het playframework niet meteen ingezet kunnen worden. Het is wel mogelijk om continuos integration te doen met het playframework, maar zonder de standaard maven plugins. Alhoewel het playframework al een goede webserver zelf heeft (daarvoor wordt netty gebruikt) is het ook mogelijk om een war te bouwen die dan gedeployed kan worden. Op deze manier kan er wel een complete deployment neergezet worden, maar dan wel met enkele scripts die dit aan elkaar kunnen praten. Dit nadeel weegt echter niet op tegenover de vele voordelen.

Informatie:
Play framework

https://github.com/rharing/play_article_java_magazine