pondělí 29. prosince 2008

Apache Wicket - Znovupoužitelné komponenty

Tvorba vlastních komponent je u komponentově orientovaného web frameworku, důležitá vlastnost. Jen težko bych hledal projekt, který by si vystačil s existujícími vlastnostmi a komponenty daného rámce a nesnažil se o vlastní tvorbu.

Pokud bych Wicket srovnával s JSF na této úrovni, musel bych více podrobněji znát tvorbu komponent v JSF. Ať jsem se snažil sebevíc, nikdy jsem úplně neodhalil výhody vlastních komponent v JSF. Ten postup mi přišel natolik složitý a nesmyslný, že v tomto případě jsem vždy sáhnul po něčem existujícím (RichFaces, Tomahawk, atd.). V JSF mám v tomto ohledu jen teoretickou znalost, která mi ovšem stačí k tomu, abych se do nečeho takového, jako je tvorba vlastních komponent, vůbec nepouštěl.

Ve Wicketu je situace naprosto rozdílná. Samotný framework mě v podstatě nutí rozsekávat danou prezentaci do několika menších celků, které se později dají znovu použít. Navíc je situace o to snažší, že není potřeba se učit či používat nové věci. Bohatě stačí znalost základních komponent a jejich využití. Vše ostatní vyplyne samo.

Pro tvorbu nové komponenty je nejčastěji používán "org.apache.wicket.markup.html.panel.Panel", od kterého má vlastní komponenta dědí. Díky tomu získám základní vlastnosti, které bych musel jinak znovu tvořit. Podobnost lze najít u Swingu, kde vlastní komponenty nejčastěji dědí od JPanelu.

Na malém příkladu ukážu, jak vytvořit komponenu, která bude obsahovat základní informace o zaměstnanci. Tuto komponentu poté budu moci využít např. při kliknutí na detail zaměstnance, při výpisu zaměstnanců, apod. Všude, kde budu chtít znát základní informace o daném zaměstnanci.

Nejprve doménový model:
public class Zamestnanec { 
private String idCislo;
private String prijmeni;
private String jmeno;
// set, get
}

Nyní vytvořím wicket komponentu:
<wicket:panel>
<span wicket:id="prijmeni"></span> <span wicket:id="jmeno"></span> (<span wicket:id="idCislo"></span>)
</wicket:panel>

A k danému HTML i Java kod:
public class ZamestnanecPanel extends Panel {
  public ZamestnanecPanel(String id, Zamestnanec z) {
  super(id);
  add(new Label("prijmeni", z.getPrijmeni());
  add(new Label("jmeno", z.getJmeno());
add(new Label("idCislo", z.getIdCislo());
  }
}

Tím mám vlastní komponentu hotovou.

Nyní ji stačí využít. Například při zobrazení detailu zaměstnance:
... hlavicka html stranky ....
<div class="detail">
<span wicket:id="zakladni"></span>
.... další vlasnosti
</div>
... paticka html stranky.....

K danému HTML i Java kod:
public class DetailPage extends WebPage {
  public DetailPage(String idCislo) {
  super();
  add(new ZamestnanecPanel("zakladni", service.findById(idCislo)));
  // dalsi vlastnosti
  }
}

I když je daný příklad triviální, jasně znázorňuje to, že tvorba vlastních komponent je v Apache Wicket velice jednoduchá a hlavně dobře použitelná. Způsob jak navrhovat takové komponenty je stejný jako v případě tvorby objektově orientovaného kódu. Třídy jsou malé a obsahují jen jednu jasně specifikovanou funkcionalitu. Stejné je to i zde.
*Myslím, že tohle je ta nejzásadnější věc, která dělá z Apache Wicketu dobrý framework, ve kterém je radost něco psát. Vytvořit komponenty a pak je skládat jako lego :)*

úterý 23. prosince 2008

JPA 2.0

Nedávno zveřejněná specifikace JPA 2.0 obsahuje asi nejzásadnější posun v možnosti psaní Criteria API. Pokud někdo očekává okopírované Criteria API z Hibernate bude možná trošku zklamán (či potěšen).

Nový způsob je totiž více postaven na "objektovosti" daných entit. Dobrý zdroj a ukázku lze najít na blogu Gavina Kinga.

Osobně toto považuji za správný krok. Samotné JPA (EntityManager) umí pouze JPQL dotazy, které jsou napsané jako jeden String řetězec. To sebou přináší hromadu nevýhod a v mnoha případech nutí, aby si vývojář napsal nějaký ten vlastní "JPQL String Parser". Z tohoto důvodu jsem osobně JPA degradoval na "pouhé" ORM mapovaní, kde mi jasně specifikovaný standard umožňuje být více nezavislý na použitém "provideru pro persistenci". EntityManager jsem vyměnil za "Hibernate Session" a vesele si psal svá Criteria API.

Hibernate Criteria API ovšem nejsou všespásná. V některých případech je tento způsob nepoužitelný. Občas člověk narazí na nějaký ten bug či extrémní složitost psaní dotazu. Jakmile vznikne požadavek na "subquery", je Hibernate Criteria API velká loterie. Samotná podpora poddotazů je totiž žalostná. Podle toho, co jsem zatím viděl, si myslím, že způsob zápisu v JPA 2.0 bude v tomto více přizpůsobivější a umožní plně nahradit jakýkoli JPQL dotaz.

Další věcí je **návratová hodnota dotazu**. Asi nikdo nepředpokládá, že lze pomocí namapovaných entit vše vyřešit. V takových chvílích nastupují DTO objekty, které se časem začnou množit. Způsob, jak výsledek převádět do vlastních objektů, je v JPA 1.0 dost špatně navržen. Psát "new cela.pakaz.az.k.ZakaznikDTO(a, zde, hromada, parametru)" je otřesné. Když k tomu ještě připočítám nutnost tvorby konstruktorů, tak jak jsou tvořeny v JPQL, dostanete se do situace, kdy Vám Java jako staticky typovaný jazyk přestane pomáhat. Budete se muset spoléhat na správnost daných JPQL dotazů i v případech refactoringu (samozřejmě testy mohou pomoci :)).

Proto i zde jsem zvolil Hibernate Criteria API, jelikož způsob návratové hodnoty lze mnohem lépe zapsat pomocí "Hibernate Projections".

Bohužel i tato volba není úplně ideální a opět obsahuje "mouchy". První z nich je asi to, že nelze vytvořit DTO objekt, který bude obsahovat referenci na jiný objekt než ten, který zná Hibernate nad JDBC (String, Integer, atd.). Zde se jaksi ztrácí ono magické OOP a nastupuje hromada primitivních DTO objektů, které mezi sebou nelze propojit jako v případě mapovaných entit. Jediné propojení lze samotřejmě provést pouze na úrovni vlastního Java kódu.

I když jsem novou specifikaci nečetl řádek po řádku, nenalezl jsem v této oblasti žádný posun. Je pravdou, že i "select statement" byl z části vylepšen, ale nikoli tak, aby umožňoval tvorbu vzájemně propojených DTO objektů.

Ale i přesto musím říct, že nové Criteria Query API se mi v JPA dost zamlouvají. Snad bude tento nový způsob zápisu alespoň schopen nahradit Hibernate Criteria API.

*Specifikace pro JPA je dosti rozsáhlá a myslím, že jako čtivo na dlouhé zimní večery bude jistě dobrou volbou :)*