17. září 2007

Zaměřte se na frontend

Potřebujete zvýšit výkon vašich stránek z pohledu koncového uživatele? Zaměřte se na frontend, ne na backend. To je obsah prezentace Steva Sounderse z Yahoo. A proč? Při analýze výkonu Yahoo stránek se ukázalo, že pouze 12% z doby potřebné pro zobrazení stránek je čas strávený generováním obsahu na serveru. Zbytek je přenos dat a práce klienta. Tento průzkum byl potvrzen na výkonu 9 z 10 TOP10 amerických webů, kdy více jak 20% zlepšení v rychlosti zobrazení stránek po natažení všeho cachevatelného do cache zaznamenal pouze Google.

A jaké jsou tedy rady:

  1. čím méně HTTP requestů, tím lépe - spojujte jvascriptové a CSS soubory, používejte CSS Sprites
  2. používejte CDN (Content Delivery Network) - distribuujte nejprve statický a pak dynamický obsah
  3. používejte Expires hlavičky - umožní cachování obsahu
  4. gzipujte posílaný obsah - 90% klientů podporuje kompresi obsahu pomocí gzipu, využijte jej
  5. CSS stylesheety dejte na začátek stránky - IE zobrazí stránku, až když má nateženy všechny stylesheety, proto je nutné je začít stahovat co nejdřív
  6. javascript naopak dejte na konec - máte-li javascript inline v dokumentu, pak blokuje renderování něčeho viditelného, je-li jako externí soubor, pak blokuje (především v IE) stahování něčeho vizuálního
  7. nepoužívejte CSS výrazy - něco jako width: expression (document.body.clientWidth < 600 ? "600px" : "auto"); příšerně zpomaluje stránku, protože takový výraz se vyhodnocuje pořád (při pohybu myši, při stisku klávesy, ...)
  8. CSS a javascript mají být v externích souborech - sice přidávají HTTP request, ale mohou být cachovány
  9. minimalizovat počet DNS lookupů
  10. Minify Javascript (případně obfuscate) - zmenší objem javascriptu, ale nemění funkci
  11. vyvarujte se redirectů - zablokují renderování stránky a oddalují jej
  12. odstraňte duplicity - ve scriptech, stylesheetech, ...
  13. snažte se používat ETags
  14. udělejte AJAX cachovatelný - i requesty prováděné pomocí AJAX by měly být cachovány
Musím říci, že tato prezentace byla pro mě překvapující, protože bych čekal, že za zvýšením výkonu stránek bude nějaká magická serverová technologie nebo vychytávka. A ejhle ono ne. Je pravda, že na přizpůsobení stránek rychlejšímu zpracování na klientu se moc velká pozornost nevěnuje, zde je taková malá kapesní kuchařka.

Tento příspěvek je spíše lákáním na shlédnutí ani ne 40 min prezentace, jistě je to zajímavé poslouchání.

PS: v prezentaci se hovoří o plugin do Firefoxu YSLOW, který analyzuje stránky a říka proč jsou pomalé, rozhodně stojí za vyzkoušení, jistě se dozvíte zajímavé věci.

Code review je jistě užitečná věc

Když jsem si přečetl dagiho příspěvek o code review vyskočil jsem jak čertík z krabičky. Než jsem došel domu, málem jsem na své rozhodnotí reagovat zapomněl, ale už jsem si zase vzpomněl.

Jsem zvyklý code review používat a považuji jej za skvělou věc. Ovšem nejprve co to je code review? Jak jej vnímám já, není to kontrola, že kód splňuje nějaký coding standard, není to náhrada testů, tj. kontrola správnosti (ve smyslu fungování) a ani to není kontrola na neexistenci známých chybových paternů. Jsem tedy plně v souladu s dagim, že code review nemá plnit úkoly, které může plnit automat (počítač) pomocí PMD, Checkstyle, FindBugs apod.

Co tedy má plnit code review? Řekl bych, s trochou nadsázky, že má plnit to co pair-programming, akorát, že prográmátoři se baví už nad hotovým kódem. Tj. ten co dělá code review, sleduje kód, jak je napsán, zda mu rozumí (podstatná věc, protože pak je pravděpodobé, že to pochopí i někdo další), zda je kód rozumně strukturován a pod. Samozřejmě se jeden učí od druhého, protože buď se koukám na kód, který mě něčím inspiruje a nebo se mi v něm něco nelíbí, požaduju změnu a inspiruji autora.

U nás v týmu provádíme code review po jednotlivých požadavcích, které programátoři plní. Každý požadavek má přidělen implementátora a reviewera. Teprve pokud je review hotové, všechny připomínky vyřešeny, pak se požadavek zavírá jako hotový.

Tento postup se nám v praxi ověřil, jako kontrolní mechanismus, jako princip vzdělávání, jako postup pro zlepšování kvality kódu. Takže závěr, code review ano, ale ne na automatizovatelné úkony.

15. září 2007

OpenAjax Hub = javascriptový messaging v 5kB

Dneska si ještě více rozšíříme znalosti na poli AJAX technologií. Z blízka se podíváme na OpenAjax Hub z pod pokličky OpenAjax Alliance. V podstatě jde o messaging pro JavaScript napsaný v JavaScriptě, nebo o jednoduchý, ale mocný, publish/subscribe hub.

Pokud děláte AJAX aplikace, které jsou složené z komponent, jenž si mezi sebou posílají události, pak čtěte dále. Pokud použijeme comet, pak komponentou může být i server (jak na to je popsáno v DWR a Jetty = výkoný AJAX naruby). Obzvláště výhodné je použití OpenAjax Hubu v případech, kdy jedna komponenta generuje události, které přijímá více komponent. Navíc hub sebou nese nezávislost jednotlivých komponent, tj. komponenty komunikují pouze s hubem a ne mezi sebou, tj. čím více komponent, tím větší zjednodušení.

My jsme měli např. problém v naší aplikaci, která sleduje pohyb autobusů (autobus se ohlašuje pomocí GPRS a posílá svou polohu získanou z GPS). Chtěli jsme polohu zobrazit nejenom v mapě, ale také pomocí tabulky, která by zobrazovala další informace (např. zda jede na čas, jak je daleko na své trase).

Jak Hub použít


Pokud se proklikáte na sourceforge stránky projektu a stáhnete si OpenAjax Hub možná budete překvapeni, protože soubor OpenAjax.js, který obsahuje implementaci Hubu má plných 5744 B. Ano není to překlep, méně než 6 kB.

Nejprve začnu serverovou částí, kde použijeme Reverse AJAX. Oproti příspěvku s chatem, kde jsme volali funkci newMsg(msg) budeme volat funkci OpenAjax.hub.publish("bus.location", busLocation) (použijeme k tomu objekt ScriptBuffer jako minule). Jako první parametr do funkce publish předáme jméno události a druhým parametrem je samotná událost (využijeme DWR, které převede java objekt do javasriptového).

Na klientu musíme zapnout reverse Ajax v DWR. Dále se zaregistrujeme k odběru událostí, což provedeme kódem:

var subscription = OpenAjax.hub.subscribe("bus.location", showBusLocationInMap);

Prvním parametrem je jméno události. Pokud se chceme přihlásit k odběru všech událostí týkajících se autobusů, pak použijeme wildcard a zapíšeme jméno události bus.*. To znamená i bus.state nebo bus.message. Pokud se chceme přihlásit i k odběru bus.data.check pak použijeme bus.**.
Druhým parametrem je callbak funkce, kterou si popíšeme dále. Dalšími parametry, které v příkladu nevyužíváme jsou:
  • scope - objekt, který bude this při volání callbacku (je-li null, pak je použit objekt window)
  • subscriberData - objekt, který je předán callback funkci jako parametr
  • filter - funkce, která vrací true/false v případě, že akceptujeme/odmítáme předanou událost
Návratovou hodnotou funkce subscribe je objekt, který se použije jako parametr při volání unsubscribe.

Callback bude mít následující podobu:

function showBusLocationInMap(name, event, subscriberData) {
//změň polohu autobusu na mapě - data jsou v objektu event
}

Nemůžu se stále ubránit dojmu, že je to geniální. Skvělý nápad, skvělá implementace, velmi jednoduché použití.

Závěr


Dneska jsem vám ukázal co to je OpenAjax Hub. Jak jej použít pro distribuci událostí na principu publish / subscribe. Ve spolupráci s DWR a jeho reverse AJAX implementací jsme navíc velmi jednoduše vytvářeli události na serveru.

To co je ESB pro distribuované systémy, to je OpenAjax Hub pro AJAX aplikace. Pokud tvoříte portálová řešení, mashupy, či složitější aplikace, pak se vám zjednodušení vnitřní komunikace, které vám nabízí OpenAjax Hub bude určitě hodit.

Odkazy


14. září 2007

DWR a Jetty = výkoný AJAX naruby

Nebudu přímo reagovat, ale spíš doplním vlastův blog DWR - AJAX knihovna pro remotování Java objektů o ,z mého hlediska hodně zajímavou ,vlastnost frameworku DWR 2.0, tj. Reverse AJAX.

Reverse AJAX je když dokážeme ze serveru volat clienta, toď velmi jednoduchá definice. A jak této skutečnosti dosáhnout? Existují 3 možné způsoby:

  • polling - asi nejstarší dostupná varianta, klient se v pravidelných intervalech dotazuje serveru, zda není něco nového - nevýhoda je zřejmá, velmi často dotaz běží na server jenom proto, aby se dozvěděl, že nic nového není
  • piggyback - dotaz na server se přiloží k nejbližšímu následujícímí regulérnímu požadavku na server - nevýhodou je, že se o změnách ze serveru můžeme dozvědět s poměrně velkým zpožděním
  • comet - vytvoří se spojení se serverem, toto spojení se neuzavře a server jej využívá k odesílání dat klientovi v okamžiku, kdy má taková data k dispozici - výhodou je, že se klient o změnách dozví okamžitě a toto řešení nevyvolává nežádoucí provoz (blíže např. dagiho příspěvek)
Ovšem přeci jenom jednu nevýhodu tento přístup má. Pokud bychom nevytvářeli aplikace "inteligentně", pak každé spojení klienta se serverem, které se používá k posílání dat ze serveru na klienta, nám použije jedno vlákno na serveru, což bude řešení, které nebude moc škálovatelné.

DWR + Jetty = výkoný reverse AJAX

Nyní se dostaneme k řešení, které framework DWR dovoluje použít, pokud je nasazen v kontejneru Jetty 6.0. Ve verzi 6.0 totiž Jetty obsahuje podporu pro continuations, které umožňují "uspat" obsloužení aktuálního požadavku a vyvolat jej později (za podpory java.nio toto uspání neznamená uspání a blokování obslužného vlákna požadavku).

Nyní se pomalu dostaneme k příkladu chat serveru. V první řadě si představíme třídu, která bude reprezentovat zprávu:
public class ChatMessage {

protected String nick;
protected String message;

public ChatMessage(String nick, String message) {
super();
this.nick = nick;
this.message = message;
}

public String getNick() {
return nick;
}

public String getMessage() {
return message;
}

}
Nic překvapujícího není k vidění. Následuje rozhraní pro příjemce nových zpráv a třída, která bude singleton a bude implementovat vstupní bránu pro všechny došlé zprávy (doručené např. AJAX requestem a nebo obyčejným odesláním formuláře - implementaci takového jednoduchého servletu nechám na vás). Jdu rovnou na ChatListener a ChatGateway:
public void class ChatListener {

public void onMessage(ChatMessage msg);

}

public void class ChatGateway {

private static final ChatGateway instance = new ChatGateway(); //singleton instance

protected List<ChatListener> lsts;

private ChatGateway() {
super();
lsts = new ArrayList<ChatListener>();
}

public void addListener(ChatListener lst) { //přidej posluchače
...
}

public void newMessage(ChatMessage msg) {
List<ChatListener> lsts2 = lsts;
for (ChatListener lst : lsts2) {
lst.onMessage(msg); //každému sděl novou zprávu
}
}

}

V tomto kódu stále ještě není žádné moudro, které by nějak přibližovalo naše řešení a proto pojďme dále k ChatTracker:
import org.directwebremoting.*;

public class CharTracker implements ChatListener {

protected static final String URL = "..."; //URL stránky, kde se chat odehrává

protected ServerContext srvCtx; //to je celé DWR kouzlo

public void ChatTracker() {
super();

//vytvoříme server context
WebContext webCtx = WebContextFactory.get();
srvCtx = ServerContextFactory.get(webCtx.getServletContext());

//gateway o nás musí vědět
ChatGateway.getInstance().addListener(this);
}

public void onMessage(ChatMessage msg) {
//vytvoříme script, který bude odeslán všem klientům
ScriptBuilder sb = new ScriptBuilder();
sb.appendScript("newMsg(").appendData(msg).appendScript(");");

//pošli script všem klientům, kteří jsou na stránce s definovaným URL
for (ScriptSession ss: serCtx.getScriptSessionsByPage(URL)) {
ss.addScript(sb);
}

}

}

V tomto útržku kódu je velmi podstatný ServerContext, což je DWR třída, která pro nás bude prostředníkem pro zjišťování, kteří klienti jsou právě na stránce chatu a má se jim poslat informace o nové zprávě. Každý takový klient je reprezentován instancí třídy ScriptSession, která je použita k posílání událostí (v našem příkladu k posílání JavaScriptového kódu).

Podstatnou drobností je metoda appendData třídy ScriptBuilder, která zakóduje Java objekt do JavaScriptové implementace, přesně v duchu DWR (viz. konfigurace dále).

Pro jednoduchost nepoužiji integraci se SpringFrameworkem a použiji přímou konfiguraci DWR pomocí souboru dwr.xml, jenž bude umístěn v adresáři WEB-INF:

<dwr>
<allow>
<create creator="new" javascript="chatTracker" scope="application">
<param name="class" value="ChatTracker"/>
</create>

<convert converter="bean" match="ChatMessage"/>
</allow>
</dwr>

S přihlédnutím k vlastově příspěvku zde není nic neznámého a představíme si definici DWR servletu ve web.xml:

<servlet>
<servlet-name>dwr-invoker</servlet-name>
<servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>
<init-param>
<param-name>activeReverseAjaxEnabled</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>initApplicationScopeCreatorAtStartup</param-name>
<param-value>true</param-value>
</init-param>
</servlet>

Servlet DwrServlet je v jiné package, než uváděl vlasta, což je dáno použitím verze 2.0. Parametr activeReverseAjaxEnabled povoluje reverse AJAX a initApplicationScopeCreatorAtStartup zařídí vytvoření instance
třídy ChatTracker při startu aplikace, nikolivěk při jejím prvním použití.

A co na straně klienta. Asi už je vám jasné, že to bude opět velmi jednoduché:

window.onload = function() {
dwr.engine.setActiveReverseAjax(true);
}

function newMsg(msg) {
if(msg) {
//zde přidej kód pro zobrazení zprávy
}
}

A je to. Je to úžasně jednoduché, to je přesně podle mého gusta. Jediné co mi dělá vrásky na čele, je implementace pouze pro Jetty 6.0, ale už existuje požadavek na vytvoření podpory pro Tomcat 6.0 (DWR-143), tak snad se v budoucnu dočkáme.

Závěr


Ukázal jsem vám, jak pomocí DWR 2.0 a Jetty 6.0 implementovat posílání událostí ze serveru na klienty na příkladu chatu. DWR vám jednoznačně hodně ulehčí v implementování detailů, čímž vám ušetří nejenom čas, ale i nervy. Navíc můžete velmi jednoduše přepínat mezi pollováním, piggybackem a cometem bez zásahu do vašeho kódu, všechny detaily jsou schované uvnitř DWR.

Odkazy

10. září 2007

Něco úvodem

Nejprve vám napíšu něco o sobě. Primárně jsem programátor. Takže se na těchto stránkách budete potkávat s různými věcmi týkajícími se programování, tj. od serveru po klienta, od Javy po SQL, od IDE po profiler atd. A abych nezapomněl, asi se tu budete potkávat i s detaily o projektu cobertura, protože to je už i moje dítě.

A proč mít vlastní blog? Vede mě k tomu pocit, že se chci podělit o svoje zkušenosti, které věřím nejsou malé a budou čím dál větší. Snad budu mít stále i chuť sepisovat své zážitky, aby zde bylo co číst.