Play framework

Play je framework sloužící k tvorbě webových aplikací běžících nad JVM a postavených na architektuře MVC. Je založen na principech REST a důraz klade na bezstavovost jednotlivých aplikací. Jako jazyk umožňuje programátorům zvolit Javu nebo Scalu, o kterou programátor i při vývoji v Javě často zavadí, jelikož je na pozadí celého frameworku a je mimo jiné použita i jako šablonovací jazyk.

Play framework
VývojářLightbend Inc.
Aktuální verze2.8.8 (8. dubna 2021)
PlatformaJava Virtual Machine
Vyvíjeno vScala a Java
Typ softwaruwebový aplikační framework
LicenceApache License, Version 2.0
Webwww.playframework.com a www.playframework.com
Některá data mohou pocházet z datové položky.

Protože Scala je zpětně kompatibilní s Javou, lze jednotlivé Java objekty používat i ve Scale. Teoreticky je tak možné celou modelovou část aplikace napsat v Javě a na řadiče a pohledy použít Scalu.

Jedná se o kompletní framework, který přichází s vestavěnou databází H2, vlastním ORM nástrojem Ebean a vlastním serverem Netty, na kterém lze aplikace během vývoje spouštět. Díky svému charakteru je často srovnáván s podobně zaměřenými frameworky, jako jsou Nette Framework, Django, Symfony nebo Ruby on Rails, a díky svým vlastnostem se hodí pro vývoj menších a středně velkých aplikací. V Play je spojen charakter vývoje typický pro podobné frameworky - rychlost, jednoduchost a lehkost a svět Javy, který je dnes vedle .NET frameworku standardem pro enterprise aplikace.

V praxi je framework používán kromě jiných firmami Linkedin nebo britským listem The Guardian.

Jedná se o open source nástroj, jehož vývoj zastřešuje společnost Typesafe. K dispozici je kromě dokumentace pro Javu i Scalu také množství ukázkových aplikací. Co se týče dalších zdrojů informací, je možné sáhnout po zahraniční literatuře. Příkladem publikací jsou Play for Java [1], Play for Scala [2] a další.

Instalace a zprovoznění frameworku

Základním předpokladem pro používání frameworku je správně nainstalovaná Java. Jeho zprovoznění je pak otázkou několika minut. Celý framework má zhruba 1,17MB. Po jeho stažení je třeba celý archiv rozbalit na libovolné místo na disku. K tomuto adresáři je třeba nastavit práva pro čtení a zápis, protože během spuštění si zde framework ukládá pomocné soubory. Posledním krokem instalace je úprava systémové proměnné PATH. Potom, co je v systému nastavena cesta k adresáři s frameworkem, je zpřístupněn také příkaz activator.

Volba IDE

Přestože Play nenutí programátory k používání konkrétního IDE, jeho volba je do jisté míry ovlivněna zvoleným jazykem. I tak je ale možné pracovat v prakticky jakémkoli textovém editoru. Co se týče třech nejpoužívanějších vývojových prostředí pro Javu, framework nalezne podporu v Eclipse, Netbeans i IntelliJ Idea, a to již přímo nebo po instalaci dodatečných pluginů. Pro práci se Scalou je možné použít i nástroj Scala IDE, který je postavený nad Eclipse.

Hello world

Pro generování základní struktury projektu je možné z konzole spustit příkaz activator new za kterým se uvede název vytvářené aplikace. Následně je možné zvolit jazyk, ve kterém bude aplikace vytvářena. Vybrat je možné mezi Javou a Scalou. V aktuální složce je poté vytvořen adresář se zadaným názvem a základní strukturou aplikace.

├──app
│ ├──assets
│ │ ├──stylesheets
│ │ └──javascripts
│ ├──controllers
│ ├──models
│ ├──views
├──conf
│ ├──application.conf
│ └──routes
├──public
│ ├──stylesheets
│ ├──javascripts
│ └──images
├──project
│ ├──build.properties
│ └──plugins.sbt
├──libs
├──logs
│ └──application.log
├──target
└──test

Nastavení aplikace

Základním konfiguračním souborem aplikace je soubor application.conf. Jedná se o běžný textový soubor, používající formát HOCON a jeho úpravou je možné měnit nastavení databázového připojení, konfiguraci nástroje Ebean, logování, nebo vlastnosti lokalizace. Základní konfiguraci databáze a ORM nástroje lze provést odkomentováním několika řádků.

#Database conf
db.default.driver=org.h2.driver
db.default.url="jdbc:h2:mem:play"
db.default.user=sa
db.default.password=""

#Ebean conf
ebean.default="models"

Za sestavení celé aplikace je odpovědný nástroj zvaný Scala Build Tool (sbt). Ten se stará o veškeré závislosti, jejich stažení, integraci a sestavení výsledné aplikace.

import play.PlayJava

name := """sqatool"""
version := "1.0-SNAPSHOT"
lazy val root = (project in file(".")).enablePlugins(PlayJava)
scalaVersion := "2.11.1"

libraryDependencies ++= Seq(
  javaJdbc,
  javaJpa,
  "org.hibernate" % "hibernate-entitymanager" % "4.2.5.Final",
  "postgresql" % "postgresql" % "9.1-901-1.jdbc4",
  cache,
  javaWs,
  "com.adrianhurt" %% "play-bootstrap3" % "0.4",
  "org.mindrot" % "jbcrypt" % "0.3m"
)

Základní příkazy konzole

Jednou možností, jak framework používat je z příkazového řádku, druhou je pak webové rozhraní. V dřívějších verzích frameworku byl pro práci s ním vyhrazen příkaz play. Ten je od verze 2.3.6 (v době psaní tohoto článku je nejnovější verze 2.3.9) nahrazen příkazem activator. Kompletní seznam příkazů lze pak vypsat pomocí activator help.

Tvorba modelu

Modelová část aplikace se odvíjí od zvoleného jazyka, v případě Javy jsou jednotlivé objekty modelu klasické Java soubory. Za kontroverzní vlastnost frameworku lze na první pohled označit to, že umožňuje deklarovat jednotlivé atributy tříd jako veřejně přístupné a dochází tak k porušení zapouzdření, jedné ze základních objektových vlastností.

Ve skutečnosti se ale autoři Play frameworku snažili programátorům maximálně ulehčit práci. V tomto případě zpřehlednit kód a zmenšit množství psaného / generovaného kódu. Přestože to tak nevypadá, při překladu jsou ke každému veřejnému atributu vygenerovány přístupové metody a práce s atributy je pak prováděna přes tyto metody. Některé frameworkzy (například Hibernate) přístupové metody vyžadují. Nic tak nebrání tomu, aby se od počátku v kódu tyto metody vyskytovaly a jednotlivé atributy byly označeny jako neveřejné (příp. soukromé).

Uvnitř modelových objektů je možné použít JPA anotace. ORM nástroj Ebean, který je součástí frameworku registruje mapované objekty s pomocí anotace @Entity. Aby mohly jednotlivé objekty využívat vlastností nástroje Ebean, měly by dědit ze třídy play.db.ebean.Model.

@Entity
public class Delivery extends Model{

  @Id
  public Long id;
  public String name;
  public String description;
  public BigDecimal price;
  @ManyToOne
  public Tax tax;
  @ManyToMany(mappedBy = deliveries)
  public List<Payment> payments;
  @OneToMany
  public List<Purchase> purchases;

  public Delivery(String name, String description, BigDecimal price, Tax tax){
    // tělo konstruktoru
  }
  public static Finder<Long, Delivery> getFinder(){
    return new Finder<Long, Delivery>(Long.class, Delivery.class);
  }
  // další metody

}

Tvorba řadičů a mapování HTTP požadavků

Controllery (Model-view-controller) jsou ve webových aplikacích postavených na architektuře MVC místem, kde jsou zpracovány jednotlivé HTTP požadavky. Jsou tak vstupním a výstupním místem aplikace a propojením dalších dvou vrstev - modelu a jednotlivých pohledů.

Ve frameworku Play jsou to pak objekty dědící od třídy play.mvc.Controller. Každý takový objekt řadiče obsahuje několik statických metod, jejichž úkolem je zpracovat příchozí HTTP požadavek a vrátit instanci třídy play.mvc.Result. Mapování jednotlivých HTTP požadavků je prováděno uvnitř souboru conf/routes. Uvnitř každé definice je HTTP požadavek na konkrétní adresu přiřazen ke konkrétní metodě konkrétního řadiče.

  #Home page
  GET    /               controllers.ApplicationController.index()
  #Product
  GET    /product/:id    controllers.ProductController.show(id : Long)
  GET    /product/all    controllers.ProductController.showAll()
  POST   /product/new    controllers.ProductController.createNewProduct()

Jednotlivé definice mohou kromě statických částí obsahovat i další parametry. Protože je Scala silně typový jazyk, je nutné u každého parametru uvést i jeho datový typ.

Reverse routing

Proces směrování jednotlivých požadavků je možné provádět i opačným směrem. Jedná se o situaci, kdy nejprve dojde k zavolání metody řadiče, která místo play.mvc.Result vrátí objekt play.mvc.Call, čímž daný HTTP požadavek vyvolá. Po překladu je ze souboru conf/routes vygenerován objekt Routes, který tyto obrácené definice obsahuje.

Doba platnosti a soubory Cookies

Protokol HTTP je ze své podstaty bezstavový, takže data, která jsou uvnitř jednoho požadavku předávána, jsou po zpracování požadavku ztracena. Pro ukládání stavů aplikace mezi jednotlivými požadavky tak slouží soubory HTTP cookie. Soubory cookies jsou obyčejné textové soubory - obsahují text a jejich velikost je omezena na 4Kb, které HTTP server ukládá na straně klienta. Tyto soubory mohou obsahovat různé textové informace, na které se lze v případě potřeby odvolávat mezi jednotlivými HTTP požadavky. U jednotlivých cookies lze také nastavit dobu, po kterou je má server na straně klienta držet.

Doba platnosti se liší podle potřeby. Nejkratším úsekem, který lze v Play definovat je Request. Data s touto platností jsou k dispozici přesně po dobu existence jednoho HTTP požadavku. Další možností je Flash, která data udržuje při životě přesně po dobu dvou HTTP požadavků. To je například doba nutná k zaslání informačních výpisů při odesílání formulářů nebo přesměrování mezi dvěma stránkami. Doba platnosti Session udržuje data do chvíle, než uživatel ukončí prohlížeč a jedná se o nejdelší časový úsek, který lze v Play aplikaci definovat. Poslední možností je pak doba platnosti Response, kterou lze použít například pro úpravu podoby odpovědi HTTP protokolu.

Implementace pohledů

Pohledy (Views) jsou vrstvou MVC architektury, která má za úkol prezentovat data uživateli v čitelné a srozumitelné formě. Ve webových aplikacích jsou to často soubory s koncovkou .html, které lze ale díky použití dalších jazyků dynamicky generovat. V prvních verzích Play frameworku byl tímto šablonovacím jazykem Groovy, který byl později nahrazen Scalou.

Každá šablona může, kromě instrukcí značkovacího jazyka definujícího statickou strukturu stránky, obsahovat ještě sadu dalších jazykových konstrukcí pro generování dynamického obsahu. Jedná se o podmínky, cykly nebo části jiných šablon. Každá takto definovaná dynamická část je ve výsledku přegenerována na statický výstup. Nevýhodou takového přístupu je, že se tento proces generování provádí při každém požadavku o tuto stránku. Play tento nedostatek řeší tím, že každou stránku generuje pouze jednou již při překladu, čímž zobrazení jednotlivých stránek urychluje. Během překladu dochází také ke kontrole správnosti jednotlivých typů dat a případnou chybu lze tak odhalit již během překladu.

Šablony ve frameworku Play jsou soubory s koncovkou .scala.html a jsou umístěné v adresáři app/views. Každou šablonu je třeba chápat jako funkci a její použití lze provést zavoláním dané funkce.

Formuláře a data binding

Práce s formulářem obvykle vyžaduje dva základní kroky. Prvním je formulář uživateli zobrazit, druhým potom zajistit jeho odeslání zpět do aplikace, včetně ověření jeho obsahu. Formuláře jsou ve frameworku Play objekty typu play.data.Form. Základem pro každý formulář je vždy některá třída z modelu, jehož data se mají s pomocí formuláře získat. K vytvoření formulářového objektu stačí v odpovídajícím řadiči implementovat metodu, která objekt formuláře pošle společně s dalšími požadovanými daty do dané šablony.

  public static Result showLoginFormPage(){
      return ok(views.html.login.render(form(Customer.class), "Login"));
  }

Po zavolání metody render je dané šabloně předán objekt formuláře. Každou šablonu lze chápat jako objekt (funkci) - lze jí předávat parametry a volat její metody.

    @helper._
    @(loginForm : Form[models.Customer], title : String)
    @main(title : String){
        <div id="content">
        @form(action = routes.User.authenticate(), 'id -> "loginForm"){
            <h3>Přihlášení</h3>
            <table>
                <tr>
                    <td><title for="user">Jméno: </title></td>
                    <td><input type="text" id="user" name="user" /></td>
                </tr>
                <tr>
                    <td><title for="password">Heslo: </title></td>
                    <td><input type="password" id="password" name="password" /></td>
                </tr>
            </table>
        }
        </div>
    }

Výše uvedený výpis je příkladem jednoduchého přihlašovacího formuláře. Znakem, který odděluje dynamické výpisy od statických je @. Příkaz @helper._ importuje vše z balíku helper. Po tomto příkazu následuje seznam parametrů, které daná funkce přijímá. Jedná se o objekt formuláře a nadpis stránky, který je použit v šabloně main. Do ní je vložen také objekt formuláře.

Hlavička formuláře obsahuje metodu, která se má zavolat po jeho odeslání. Po překladu odpovídá HTML kódu <form action="/login" method="POST" id="loginForm">Adresa URL, na kterou se mají data z formuláře posílat je určena podle směrovacího souboru, který by v následujícím případě měl mít následující podobu.

#Login
GET /login controllers.Login.login()
POST /login controllers.Login.authenticate()

Pro vytvoření formuláře je možné použít jednak klasické HTML elementy, jako je tomu v předchozím příkladu nebo využít formulářové konstruktory, které jsou součástí Play. Stejného výsledku jako v předchozím příkladu lze dosáhnout i touto cestou.

@helper._
@(loginForm : Form[models.Customer], title : String)
@main(title : String){
    <div id="content">
    @form(action = routes.Login.authenticate(), 'id -> "loginForm"){
        <h3>Přihlášení</h3>
        @inputText(loginForm("user"), 'label -> "Jméno: ")
        @password(loginForm("password"), 'label -> "Heslo: ")
    </div>
    }
}

Posledním krokem práce s formulářovými daty je jejich validace. Správnost dat lze ověřit buď na straně klienta nebo na straně serveru. Obecně platí, že by se tyto dva způsoby měly kombinovat. Ověření na straně serveru lze v Play provést například následujícím způsobem v metodě controlleru.

public static Result authenticate(){
    Form<Customer> bindForm = Form.form(Customer.class).bindFromRequest();
    if(bindForm.hasErrors()){
        flash("error", "Vyplňte prosím, všechna pole formuláře.");
        return redirect(login.render(bindForm,"Neplatné přihlášení"));
    }
    Customer candidate = bindForm.get();
    if(validCustomer(candidate) == null){
        flash("error", "Nesprávné jméno nebo heslo");
        return redirect(login.render(bindForm, "Neplatné přihlášení"));
    }
    session.clear();
    session("user", bindForm.get().username);
    return redirect(routes.Application.customerDetail());
}

První řádek metody vytvoří odkaz na objekt formuláře získaného z příchozího HTTP požadavku. K tomu slouží metoda bindFromRequest(). Následuje první ověření. Na každý objekt typu play.data.Form je možné zavolat metodu hasErrors(), která vrací hodnotu true v případě, že daný formulář obsahuje chyby. Může se například jednat o situaci, kdy uživatel nevyplnil povinné pole formuláře.

V případě, že jsou data formuláře neúplná, lze na toto uživatele upozornit s použitím souborů cookies. Metoda flash("error", "Vyplňte prosím, všechna pole formuláře.");stanovuje dobu platnosti chybové hlášky na dva HTTP požadavky. Poté je uživatel přesměrován zpět na stránku s formulářem.

Metoda validCustomer(), která je ve výpisu použita, má za úkol v databázi zákazníků najít toho, jehož přihlašovací údaje jsou shodná jako ty zadané do formuláře. Pokud takový není nalezen, dojde opět k přesměrování zpět na stránku s přihlašovacím formulářem. V opačném případě je vytvořen soubor cookies s přihlašovacími údaji zákazníka. Jeho doba platnosti je nastavena do chvíle, než je aplikace (prohlížeč) ukončena, nebo nedojde k odhlášení.[3][4]

Reference

  1. LEROUX, Nicolas; DE KAPER, Sietse. Play for Java. [s.l.]: Manning Publications Co., 2014. 320 s. Dostupné online. ISBN 9781617290909.
  2. HILTON, Peter; BAKKER, Erik. Play for Scala. [s.l.]: Manning Publications Co., 2013. 328 s. ISBN 9781617290794.
  3. REELSEN, Alexander. Play Framework Cookbook. [s.l.]: Packt Publishing, 2011. ISBN 9781935182702.
  4. Oficiální dokumentace Play [online]. [www.playframework.com Dostupné online].
This article is issued from Wikipedia. The text is licensed under Creative Commons - Attribution - Sharealike. Additional terms may apply for the media files.