pátek 11. ledna 2013

Dependency injection není pouze o jednoduším testování

V sítu služby GetPrismatic mi uvízly články na témá dependency injection a to konkrétně Dependency injection is not a virtue (David Heinemeier Hansson), The DI Opposition a Dependency injection and other Java necessary evils. Pointa těchto článků je v tom, že v staticky typovaných jazycích, jako je například Java, se používá dependency injection z důvodů snazší testovatelnosti. David Heinemeier Hansson uvádí jako příklad vytvoření instance konkretního typu např.Date date = new Date();, které je nemožné mockovat. Ten samý problém v dynamicky typovaných jazycích odpadá, protože je možné překrýt konstruktor a tím pádem podstrčit mock. Z toho autoři vyvozují, že dependency injection není pro dynamicky typované jazyky potřeba, a dokonce by se mělo pokládata za anti-pattern.

Dynamické jazyky, a vlastně jakýkoliv jazyk, který vám v daném kontextu umožní překrýt daný konstruktor, bezesporu testování ulehčuje. Háček je v tom, že jednoduchost testování je pouze jedním z přínosů dependency injection. Mnohem zásadnější přínos je rozvolnění vazeb mezi objekty, což je samozřejmě mimo rozlišovací schopnost dynamicky typovaných jazyků. Mezi další přínosy patří kontextové řízení vazeb, jinými slovy, v závislosti na kontextu potřebuji jeden a ten samý typ, ale pokaždé jinak nastavený. Dalším přínosem je řízení životního cyklu objektů (singleton, prototyp) a jejich vazeb. Jednoduchý příklad s vytvořením datumu svádí k zamítnutí dependency injection, ale pro složitější případy, kdy se jedná více propojených objektů s různou konfigurace, davá dependency injection perfektně smysl. To už nemluvím o tom, že část konfigurace je možné díky dependency injection externalizovat úplně mimo kód aplikace.

V staticky typovných jazycích má dependency injection a Inversion of Control nezastupitelné místo. Má ho bezesporu i v dynamicky typovaných jazycích, jenom to není z důvodů jednoduchosti testování, ale z dalších důvodů, o kterých jsem mluvil. Tyto důvody jsou totiž nezávislé na typovosti daného jazyka.

neděle 6. ledna 2013

Tvorba rozšíření pro Jenkins


Na vlastní kůži jsem si zkusil, že psaní vlastních rozšíření pro Jenkins není žádný med. Pokud pro vaší potřebu existuje nějaké alespoň trochu podobné rozšíření, nepouštějte se do psaní vlastního, ušetříte si spoustu práce. To bych rád předeslal úvodem. Pokud si chystáte napsat vlastní rozšíření nebo vás jenom zajímá, jak složité by to bylo, čtěte dále.


Jenkins technologie

Představte si ten nejbizarnější technologický stack který znáte, vynásobte ho dvěma, a máte přibližně slepenec toho, co najdete v Jenkinsu. Bohužel v tomto případě se na Jenkinsu podepsalo negativně několik faktorů.

Velké množství rozšíření
Pro Jenkins existuje přes 600 veřejně dostupných rozšíření. To jeho tvůrcům znesnadňuje dělat větší zásahy. Pokud by porušili zpětnou kompatibilitu, byla by rázem jedna z největších výhod ta tam.
Doba vzniku
Nikde jsem nenašel oficiální datum, ale odpovídalo by to přibližně letům 2006 nebo 2007. Tehdy nebylo zřejmé, případně si to hlavní tvůrce Kohsuke Kawaguchi dostatečně neuvědomil, jaký dopad bude mít AJAX na interaktivitu chování aplikací a jejich vlastní návrh. Pokud máte celou prezentační vrstvu orientovanou jako page-centric, kontrolery počínaje a šablonovacím systémem konče, těžko se vám to mění.
Not Invented Here
Prakticky z čehokoliv, co v Jenkinsu používáte, na vás dýchne velmi silně syndrom Not Invented here. Autor nebo autoři, pokud si nenapsali něco vlastního, šáhli po té nejobskurnější knihovně, která byla v tu chvíli k dispozici viz šablonovací systém.
Technologická fragmentace
Když se podíváte do adresáře s knihovnami třetích stran, které Jenkins používá, najdete tam například dvě knihovny pro Dependency injection nebo několik knihoven pro práci s XML. To samé v bledě modrém se týka i knihoven pro pohodlnější práci s JavaScriptem. Působí to dojmem, že tvůrci nevěděli kudy kam, a tu se použila jedna knihovna a tu jiná v závislosti na tom, co se zrovna více líbilo.

Šablonovací systém

Pro generování HTML se používá technologie Apache Jelly, která mohla být sexy v době deziluze z JSP 1.0, ale nikoliv pět let po ní. Z oficialní dokumentace se jedná o a tool for turning XML into executable code, která si vypůjčuje mnoho dobrých konceptů z JSP custom tags, Velocity, Cocoon, Ant. Pokud jste někdy psali if-else nebo & v XML, pak tušíte, co vás čeká a nemine. Mě okamžitě naskočily osypky z dob, kdy se ještě v XML běžně programovalo. Prototypy tohoto přístupu byly Ant nebo XSLT, ke kterým se Jelly hrdě hlásí. Pokud nevíte co vás čeká, odkážu vás asi na pět let starý článek Programování v XML - špagety kód na druhou.

Nevýhodou Jelly je velmi špatná dokumentace. Poslední aktualizace je tři roky stará. Pokud hledáte nějaký vestavěný tag, najdete v dokumentaci jenom velmi povrchní popis. O nějakých příkladech se vám může jenom nechat zdát. Ještě bych uvedl odkaz na článek Jelly: Scripting for the Soulless.

Alternativou k Jelly je použití Groovy. Bohužel jako u většiny dalších věci v Jenkinsu narazíte na chybějící dokumentaci. V tomhle případě je to fatální, protože se nedá ani nic vygooglit. Takže skončíte s tím, že víte, že by se to dalo použít, ale nevíte jak.

Update 7.1.2013: Karel Rank mi dal tip na nástroj pro konverzi Jelly views na Groovy views Jenkins – Jelly to Groovy.

Tři odkazy, které by se mohly hodit, pokud budete začínat s Jelly v Jenkins.

Stapler

Stapler je, světe div se, knihovna Kohsuke Kawaguchiho, která "staples" your application objects to URLs, making it easier to write web applications. The core idea of Stapler is to automatically assign URLs for your objects, creating an intuitive URL hierarchy. Kdybych měl použít nějaké přirovnání, pak je Stapler MVC framework, který se snaží tvářit, že to vlastně MVC framework není.

Každé rozšíření do Jenkinsu má dvě části - aplikační a prezentační. Stapler se stará o jejich propojení. Zajišťuje použití konkrétní šablony pro vygenerování HTML (HTTP odpověď) a předává řízení zpracování do aplikační části (HTTP požadavek). Problém je v tom, že celé řízení zpracování probíhá automagicky, tj. convention over configuration se zvrhlo. Existuje jednoduché pravidlo, pokud je FQN třídy vašeho rozšíření cz.dagblog.plugin.HelloWorld, pak se její view hledá v adresáři odpovídajícím jejímu package, v tomto případě cz.dagblog.plugin. Vlastní jméno souboru se šablonou závisí na tom, jaký typ extenze píšete. Uvedu několik rad, které by se vám mohli hodit.

Obsluha odeslání formuláře

Pokud potřebujete obsloužit formulář, musíte nadefinovat metodu s názvem začínajícím na do a končícím na cestu, na kterou formulář odesíláte.

public void doFoo(final StaplerRequest rq, final StaplerResponse rs){}

Redirect na stránku

Pokud se potřebujete vrátit zpátky na stránku, z které jste obsloužili požadavek.

public void doFoo(final StaplerRequest rq, final StaplerResponse rs){
  rs.forwardToPreviousPage(rq);
}

Životní cyklus rozšíření

Ať již rozšíříte Jenkins v kterémkoliv bodě, narazíte na několik zvláštností.

  • Každé rozšíření resp. instance její třídy se automaticky serializuje/deserializuje z/na disk pomocí technologie XStream. Nespoléhejte na standardní životní cyklus objektů nebo se šeredně spálíte. Díky serializaci se nevolá konstruktor. Všechny instanční proměnné se naplní ve chvíli, kdy se třída vašeho rozšíření deserizalizuje. K deserializaci dochází při bootu serveru, k serializaci při každé změně konfigurace.
  • Třída vašeho rozšíření je dostupná odpovídající Jelly šabloně. V době renderování odpovědi na ní může volat libovolné metody. Jinak řečeno tato třída je zároveň kontrolerem a zároveň modelem.
  • Rovnou zapomeňte na dependency injection. Pokud potřebujete získat instanci nějaké další třídy, musíte se jí vytvořit a nebo k ní přistoupit přes singleton. To vede k těžko testovatelnému kódu a vy máte pocit, že se vrací doba kamenná.
  • Pokud potřebujete použít nějakou knihovnu třetích stran, pak si dejte pozor na chování classloaderu. Ten poměrně nelogicky upřednostňuje knihovny na cestě Jenkinsu oproti knihovnám pluginu. To se dá změnit nastavením Maven Jenkins pluginu.

JavaScript, AJAX

Podpora JavaScriptu a AJAXu nijak nevybočuje z nastoleného trendu. K dispozici je Prototype a YUI 2. Naštěstí jako tvůrci rozšíření se tím nemusíte omezovat a můžete použít framework podle vlastních představ. Podpora AJAXu se redukuje na volání anotovných metod na třídě vašeho rozšíření. Na straně šablony se vygeneruje JavaScriptový kód, který volání zapouzdřuje. Je to sice sympatické, ale působí to dojmem, že to je dolepované bokem. Pokud byste chtěli kompletní asynchronní komunikaci s výměnou JSONu, bylo by to dost komplikované.

Dokumentace

Úroveň dokumentace je žalostná. Nevím jestli to přičítat odštěpení Jenkinsu od původního Hudsonu nebo jestli je ten důvod jiný. Dokumentace existuje, ale je neuveřitelně fragmentovaná a musíte umět číst mezi řádky, aby jste tam našli to co hledáte. Protože většinou nevíte co hledáte a jenom tápete, tak je vám k ničemu. Existuje v podstatě jediný způsob, jak tohle omezení překonat. Vyhledáte se plugin, který děla něco podobného, co potřebujete právě udělat, a potom se podíváte přímo do jeho zdrojového kódu. Jedná se třeba o prkotiny typu zobrazení vlastního obrázku, obsluha formuláře, skrytí nějaké části standardního UI atd. Dobré je mít k dispozici přímo zdrojové kódy Jenkinsu, v kterých se dá ledascos vyhledat.


Závěr

Přestože by se z výše uvedených řádek mohlo zdát, že Jenkins je špatná technologie, není tomu tak. Pokud jste koncoví uživatelé Jenkinse, tak vás tato omezení vůbec trápit nebudou. Pokud budete psát vlastní rozšíření, pak se stačí obrnit velkou dávkou trpělivosti. Metodou pokus omyl se ke kýženému výsledku skoro vždy doberete.

Jako vývojové prostředí jsem používal Eclipse a Jenkins k němu moc přívětivý není. Nastartovat Jenkins embdovaně se mi nepodařilo, proto přicházel k ruce Maven. Mnohem větší problém představovalo ladění pluginu.
Restartování produkčního serveru nepřipadalo v úvahu. Proto jsem potřebné joby a jejich výsledky zkopíroval, aby byly k dispozici lokálně. Tam kde jsem potřeboval vyzkoušet integraci, jsem volal produkční Jenkins vzdáleně. Napsal jsem si vlastní proxy, která provedla podle konfigurace lokální nebo vzdálené volání.