2014. november 20., csütörtök

Változékony Grails alkalmazás konfiguráció facepalm

Az egyik Grails-es alkalmazásunk furán kezdett viselkedni, ezért nyomozás indult a cégen belül, és hamarosan meg is lett a furcsaság forrása. Az egyik gsp-ben szükség volt egy lista típusú konfiguráció kiíratására, de más sorrendben mint ahogy az rögzítve lett. A fejlesztő kolléga fogta és átrendezte az eredeti konfigurációt, amit a Grails készségesen meg is tesz. Felteszem a kérdést, hogy vajon ez a helyes működés? Egyfelől méltán tükrözi a Groovy és a Grails nyílt filozófiáját, és nem mellesleg hatékonyabbá teszi a fejlesztést is, hiszen futás-időben az egész konfiguráció felépíthető az alkalmazás újraindítása nélkül. Másfelől pedig rés a pajzson, hiszen bármelyik futó szál kénye kedve szerint módosíthatja az alkalmazás működését, ami akár az alkalmazás biztonságát is veszélyeztetheti. Számtalan Grails plugin (pl. Spring Security) tárolja ugyanitt a beállításait, és ezen beállítások kulcsai mind nyilvánosak, tehát bárki számára elérhetőek és módosíthatóak. Ha ez nem volna elegendő, akkor a ConfigObject még csak nem is szál-biztos. Amellett, hogy a ConfigObject belső működése nem szál-biztos, a tényleges konfiguráció tárolására LinkedHashMap-et használ, aminek a dokumentációjából idézve:
Note that this implementation is not synchronized. If multiple threads access a linked hash map concurrently, and at least one of the threads modifies the map structurally, it must be synchronized externally. This is typically accomplished by synchronizing on some object that naturally encapsulates the map. If no such object exists, the map should be "wrapped" using the Collections.synchronizedMap method.
Lehet én vagyok maradi, de valahogy ez az egész több sebből vérzik. És, hogy egy kis kód is legyen, az úgy még tudományosabb:
class TestController {
        def index() {
                def c1 = grailsApplication.config
                def c2 = grailsApplication.config
                c1.asd = "newconfig"
                render c2.asd
        }
}

2014. július 30., szerda

Groovy funkcionális eszközök

A Groovy sorozatot folytatva (1, 2) ebben a bejegyzésben a nyelv funkcionális aspektusát szeretném bemutatni a teljesség igénye nélkül. Két fontos tulajdonság képezi az alapját a funkcionális programozásnak Groovyban, az egyik, hogy van lehetőség anonim funkciók (Closure) írására, a másik pedig, hogy az utolsó kifejezés értéke automatikusan visszatérési érték lesz, ha nincs explicit visszatérési érték meghatározva (üres Closure visszatérési értéke null). Ez a két tulajdonság elengedhetetlen ahhoz, hogy funkcionális szemléletben tudjunk programozni, de pusztán e két dolog használata még nem eredményezi automatikusan a szemlélet-váltást. Vegyük sorra milyen egyéb eszközökkel támogatja a Groovy a munkánkat.
  • Először nézzük a Closure összefűzést:
    def m = { "${it}-" }
    def p = { "${it}+" }
    def pm = m << p
    
    println pm('') == m(p('')) // true
    
    Természetesen a másik irányba is működik a dolog:
    def mp = m >> p
    println p(m('')) == mp('') // true
    
  • A Closure.curry() metódus becsomagolja a Closure példányt, és az eredeti Closure paraméterei helyére lehet fix értékeket beállítani. A példa magáért beszél:
    def plus = { int f, int s, int t ->
        println "$f $s $t"
        return f + s + t
    }
    def fix = plus.curry(0, 0) // további opciók: ncurry() és rcurry()
    println fix(5) // 0 0 5
    
  • Felmerülhet az igény, hogy már meglévő metódusokból csináljuk Closuret. Nem a legelegánsabb megoldás, de mindenféleképpen hasznos ha meglévő eszközeinket szeretnénk "modernizálni":
    class o {
        void f() {
            println 'called'
        }
    }
    
    def c = o.&f // vagy new o().&f
    
    println c.class // class org.codehaus.groovy.runtime.MethodClosure
    
  • A funkcionális programozásra igen jellemző a rekurzív végrehajtás, és ezen programozási nyelvek részét képezik a különböző optimalizációs eszközök. Természetesen a Groovyban is van lehetőségünk finomhangolni rekurzióinkat. Az első ilyen eszköz, amit bemutatok a Closure.memoize(), ami nemes egyszerűséggel annyit tesz, hogy a visszaadott csomagoló Closure gyorsítótárazza a végrehajtás eredményeit. Különös tekintettel kell lennünk használatakor a memória-szivárgásokra, mert alapértelmezetten nincs méret határ szabva a gyorsítótárnak:
    def a = { print "called" }.memoize() // vagy memoizeBetween(int protectedCacheSize, int maxCacheSize)
    a();a() // called
    
    def a = { i -> print "called $i " }.memoize() // vagy memoizeAtLeast(int protectedCacheSize) és memoizeAtMost(int maxCacheSize)
    a(1);a(2);a(2) // called 1 called 2
    
    Meglévő metódusainkat pedig a @Memorized annotációval tudjuk hasonló működésre bírni, mely két opcionális paramétert vár a maxCacheSize és a protectedCacheSize.
  • A rekurzív hívásoknak van egy igen kártékony mellékhatása a JVMben. Minden egyes hívás rákerül a stackre, ami könnyen StackOverflowErrorhoz vezet. Ezt elkerülendő a Closure.trampoline() segítségével referenciát szerezhetünk egy olyan TrampolineClosurera, mely szekvenciálisan hívja az eredeti Closuret egészen addig, míg az TrampolineClosuret ad vissza. Ezzel a technikával mentesíti a stacket, lássuk ezt a gyakorlatban:
    def s
    s = { l, c = 0 ->
        l.size() == 0 ? c : s(l.tail(), ++c)
    }.trampoline()
    
    println s(1..10) // 10
    
    A Closure.trampoline() metódus mintájára az @TailRecursive annotációt használhatjuk, a dokumentációban szereplő megkötésekkel.
  • A funkcionális nyelvek általában az un. lazy evaluation szemléletet követik, ami röviden annyit jelent, hogy csak akkor értékel ki a rendszer valamit, ha arra feltétlenül szükség van. A Groovy is biztosít megoldásokat a paradigma követéséhez.
    def l = [].withDefault { 45 }
    println l[3] // 45
    println l // [null, null, null, 45]
    
    Vagy a @Lazy annotációval tetszőleges adattagot varázsolhatunk lusta kiértékelésűre. Egy opcionális paraméterével akár puha referenciában is tárolhatjuk az értéket, természetesen az alapértelmezett működés szerint erős referenciát alkalmaz:
    class Person {
        @Lazy(soft = true) pets = ['Cat', 'Dog', 'Bird']
    }
    
    def p = new Person()
    println p.dump() // <Person@7b073071 $pets=null>
    p.pets
    println p.dump() // <Person@18e30556 $pets=java.lang.ref.SoftReference@3f0e6ac>
    
    Annyit mindenféleképpen meg kell még jegyeznem, hogy ha a mező statikus, és nem puha referenciában történik a tárolás, akkor a Initialization on demand holder mintát követi a fordító.
A beépített funkciók után térjünk át a haladó technikákra. Bár a Groovynak szoros értelemben nem része a GPars keretrendszer, mégis érdemes kicsit közelebbről megismerkedni vele. A dokumentációból idézve:

"GPars is a multi-paradigm concurrency framework, offering several mutually cooperating high-level concurrency abstractions, such as Dataflow operators, Promises, CSP, Actors, Asynchronous Functions, Agents and Parallel Collections."

  • Meglévő Closurejainkat könnyedén aszinkron hívásokká alakíthatjuk a GParsExecutorsPool segítségével, ahogy a példa is mutatja.
  • Collectionök párhuzamos feldolgozására a GParsPoolt tudjuk segítségül hívni. A GParsPool osztály ParallelArray alapon végzi a műveleteket, míg párja a GParsExecutorsPool hagyományos thread poolokat használja.
  • A GPars része egy a Java Fork/Join könyvtárára épülő magasabb absztrakciós réteg. Ez a köztes réteg -mely a mindennapi fejlesztést hivatott megkönnyíteni- használata során nem kell szálakkal, poolokkal, és szinkronizációval bajlódnunk. Részletek a dokumentációban találhatók.
  • A Dataflow egy alternatív párhuzamos feldolgozási szemlélet. Szépsége az egyszerűségében rejlik, apró párhuzamos feladatokra bonthatjuk az alkalmazásunkat, és amikor az egyik darabka még egy ki nem értékelt adathoz szeretne hozzáférni, akkor blokkolt állapotba kerül amíg egy másik ki nem értékeli azt. Működéséből adódóan nincs verseny helyzet, nincs Dead és Live Lock sem többek között. Megkötés, hogy csak egyszer adhatunk értéket a DataflowVariable életciklusa során.
  • Az Agentek szál-biztos, nem blokkoló párhuzamosítást tesznek lehetővé, és ehhez annyit kell tennünk, hogy az osztályunkat a groovyx.gpars.agent.Agentből származtatjuk. Fontos különbség a Dataflow modellhez képest, hogy az Agent tartalma tetszőlegesen változtatható.
  • Természetesen elmaradhatatlan kellék a méltán népszerű Actor modell. Leegyszerűsítve az Actorok üzeneteket fogadnak, továbbítanak, és válaszolnak azokra. Minden üzenet bekerül egy sorba, majd onnan a feldolgozásra. A megoldás előnye, hogy implicit szál-biztos, nem blokkolt, és nincs szükség szinkronizációra sem a soros feldolgozás miatt. Lényeges tulajdonsága az Actor rendszernek, hogy nem hagyományos szál-kezelést használ, hanem saját maga menedzseli a feladatokat. Létezik állapot-tartó, és állapot-mentes Actor egyaránt.
Ahogy a Barátkozás a Groovyval bejegyzésben is leírtam, a Groovy nem kezdő programozók kezébe való eszköz. Szép és jó ez a sok beépített okosság, de az alapok ismerete nélkül csak még jobban összezavarnak mindent, megnehezítik a hiba feltárást és az elhárítást is. Remélem sikerült kedvet csinálnom a téma mélyebb megismeréséhez, ráadásként pedig egy igazi "ínyencséget" hagytam:
def deliver(String n) {
 [from: { String f ->
     [to: { String t ->
         [via: { String v ->
             println "Delivering $n from $f to $t via $v."
         }]
     }]
 
 }]
}
deliver "Béla" from "Mezőberény" to "Kisfái" via "Traktor" // Delivering Béla from Mezőberény to Kisfái via Traktor.

2014. május 7., szerda

import groovy.transform.Immutable

A Groovy szemlélet ellentétben a Java-val a nyiltságról szól. Minden alapértelmezetten publikus, nem kell bajlódnunk getter/setter írásával (persze a háttérben történnek dolgok, most a szemléletről beszélek), a metaclass jóvóltából módosítani lehet osztályok működését, és még sorolhatnám. A most bemutatásra kerülő lehetőség mégis sokszor tud hasznos lenni.
@groovy.transform.Immutable
class A { Integer a }
new A()
A kódot futtatva az alábbi eredményt kapjuk:
org.codehouse.groovy.runtime.metaclass.MethodSelectionException: Could not find which method <init>() to invoke from list:
public A#<init>(java.util.HashMap)
public A#<init>(java.lang.Integer)
Tehát a fordító nem tudta eldönteni, hogy melyik konstruktorát használja az osztálynak, ami azt jelenti, hogy az ilyen intefésszel ellátott osztályok tagjainak kötelező kezdő értéket adnunk.
@groovy.transform.Immutable
class A { Integer a }
new A(0).a = 1
Mikor megpróbáljuk módosítani az adattagot, egy kivétellel hálálja meg a JVM.
groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: a for class: A
A történet eddig nem nagy szám, mert az alábbi kódsorok ugyanezt az eredményt adják (kicsit több gépeléssel):
class A {
final Integer a
A(a) { this.a = a }
}
new A(0).a = 1
De mi a helyzet ezzel:
@groovy.transform.Immutable
class A { List a }
A a = new A([])
a.a << 1
Ennél az esetnél mutatkozik meg, hogy az annotáció mivel több egy mezei final módosítónál. A listánk bizony egy Collection.UnmodifiableRandomAccessList lett. Tovább kísérletezve mi történik, ha egy saját osztályt teszünk adattagként A-ba.
@groovy.transform.Immutable
class A { B b }
class B { Integer b }
A a = new A(new B())
java.lang.RuntimeException: @Immutable processor doesn't know how to handle field 'b' of type 'B' while constructing class A.
@Immutable classes only support properties with effectively immutable types including:
- Strings, primitive types, wrapper types, BigInteger and BigDecimal, enums
- other @Immutable classes and known immutables (java.awt.Color, java.net.URI)
- Cloneable classes, collections, maps and arrays, and other classes with special handling (java.util.Date)
Other restrictions apply, please see the groovydoc for @Immutable for further details
Amennyiben ellátunk egy osztályt ezzel az annotációval, a Groovy fordító garantálja nekünk, az osztály módosíthatatlan lesz, hiszen kötelezően minden adattagjának szintén módosíthatatlannak kell lennie. Bővebb információ a GroovyDoc-ban, amit fontos kiemelni, hogy @Target({ElementType.TYPE}), tehát osztályokra, interfészekre, és enumokra lehet tenni az annotációt, bár utóbbi 2 esetben fordítási hibát fogunk kapni.
Házi feladat a a groovy.transform csomag átböngészése, sok hasznos eszköz lapul még benne.

2014. április 23., szerda

groovy.lang.Tuple kicsit közelebbről

Belefutottam egy aranyos Groovy osztályba, a Tuple-be. Ha jobban megvizsgáljuk az öröklési láncot láthatjuk, hogy a Tuple lényegében egy AbstractList implementáció, és arra való, hogy összetartozó értékeket tároljunk benne. Példányosításkor meghatározzuk a tartalmát, amit a későbbiekben nem lehet módosítani. Kisértetiesen hasonlít a Collections.unmodifiableList()-hez, de a következő példa jól szemlélteti a Tuple eleganciáját:
Collections.UnmodifiableRandomAccessList foo() { Collections.unmodifiableList([]) }
Ezt ne próbáljátok meg lefordítani, nem fog sikerülni. A probléma az, hogy a UnmodifiableRandomAccessList a Collections belső osztája, ha pedig List-re cseréljük a visszatérési érték típusát, akkor a hívó fél részére nem lesz egyértelmű, hogy a lista nem módosítható, vagy ne adj isten egy verzióváltás során lesz UnmodifiableRandomAccessList, és a működő kód megy tönkre (sose láttunk még csak hasonlót sem). Itt jön az elegancia:
Tuple foo() { new Tuple() }
Ez az egyszerű példa megmutatja mi mindenre alkalmas többek között a Tuple, ha a munkatársainkat szeretnénk kicsit megviccelni (dynamic type rulz)
def x = (a,b,c) = { x -> new Tuple(x*10,"${x*20}") } << 5
println "$a,$b,$c,$x"

2014. január 18., szombat

Ping-conf Day 2

A második nap a Ping-conferencián hozta az első nap színvonalát. Yevgeniy Brikman izgalmas demóval egybekötött előadása mindenkit lázba hozott korán reggel. Prezentációját azzal kezdte, hogy elmesélte, hogy másfél éve használják a Play Frameworköt LinkedInnél több mint 60 projekthez nagy sikerrel. Megnevezett két problémás területet tapasztalataik alapján:
  • Nehezen kezelhető komplexitás (szerintem ez nem feltétlen csak Playben jött volna elő az ő esetükben)
  • Borzalmas teljesítmény
Az első ponttal kapcsolatban megmutatta, hogy a LinkedIn oldalán rengeteg apró kis tartalmi rész van, amik ráadásul eltérnek a különböző felhasználó típusonként (újonc, prémium, etc), ezért nem lehetséges egyetlen kontrollerbe tenni a logikát (hecc kedvéért kipróbálták, és a fordító elszállt a scala fájl méretére hivatkozva). A megoldás, amit használnak, hogy minden egyes kis doboz önállóan is él, tehát van saját kontrollere, nézete, meg minden ami kell. Nagy előnye a módszernek, hogy nagyon könnyen tesztelhető, továbbá nagyon könnyen skálázható vízszintesen, ugyanis az egyes modulok szétoszthatóak több szerver között. A publikus oldalak kontrollereiben pedig egyszerűen legenerálják az egyes részeket, és összefűzik a kiszolgálandó HTMLbe. A technika hátulütője, hogy a statikus erőforrásokat a kontrollerben kell kezelni (szerk,. megj. illetve nem lehet ezen erőforrások tömörítését, egybefűzését valamilyen pluginnel leegyszerűsíteni).
Ezt a fragmentációs technikát választva az alábbiak igényelnek külön odafigyelést:
  • Cookiek esetén szükségük van egy metódusra, ami a darabkák headerjeiből összefűzte a Cookie bejegyzéseket
  • Statikus erőforrások beillesztésére szintén a headerbe kell tenni  X.CSS ill. X-JS bejegyzéseket, amit aztán összefűznek, deduplikálják, és a HTML headbe tesznek (erről később, hogy miért pont oda)
  • Hibakezeléshez a legfelső réteg kiolvassa a darabkák státsz-kódját
Miután végigmentünk a komplexitás témakörén, áttértünk a teljesítmény problémákra. Első lépésben átalakította az alkalmazást, hogy a hagyományos String alapú HTML generálás helyett streamelje a kimenetet. A Scala nyelvben vannak natív eszközök, mindössze egy custom render templatet kellett bekötni a Playbe (az alapértelmezett StringBuildert használ), és az Enumerator, Future párossal megoldotta, hogy a szerver az előállított HTMLt azonnal küldte is a kliensnek. Akit érdekel részletesebben a dolog itt tud utánaolvasni. Ez azért növeli a teljesítményt, mert a HTML headben elhelyezett erőforrások letöltésével nem kell a kliensnek megvárnia, amíg minden kis darabka összeáll. Következő trükk, amivel tovább fokozta a teljesítmény és a felhasználói élményt eléggé leleményes. A darabkák kimenetét a HTML bodyn kívülre, egy script type="text/html-stream" elembe tette, majd JavaScripttel a helyükre illesztette, amikor végzett velük a szerver. A módszer előnye, hogy nincs szükség darabonként egy AJAX kérésre, így tehermentesíthető a szerver, mégis a felhasználó azonnal kap választ a kérésére. Amire érdemes odafigyelni, hogy a hirtelen a helyükre kerülő elemek miatt ugrálhat az oldal, megzavarva ezzel az éppen kattintani vágyó felhasználót, valamint a headerek már a kérés elején kiküldésre kerülnek, ezért azokat a kérés további részében nem lehet változtatni. Nehezebb az oldalt tesztelni, és SEO problémák is felmerülhetnek. A demóalkalmazás egyébként elérhető githubon.

A következő prezentációt James Proper tartotta, aki szintén a teljesítmény-optimalizációt választotta témájául. Az első dolog, amit szép teljesítmény-tesztekkel bemutatott, hogy az aszinkron nem egyenlő a gyorsasággal, sőt! Egy egyszerű példán keresztül bebizonyította, hogy az aszinkron kérés mennyivel tud lassabb lenni a sok context switch miatt, amik elveszik a drága processzoridőt. Folytatásként több módszert is bemutatott, amivel növelhető a teljesítmény, és elkerülhető a rengeteg context switch.
  • Az első javaslata az volt, hogy használjuk a Scala beépített ExecutionContextjét, ami a Java Fork/Join osztályaira épül. Bővebben
  • Második lehetőségként azt ajánlotta, hogy ne használjuk az első pontban említett contextet, hanem váltsunk ImmediateExecutionContextre
Általános jó tanácsként említette, hogy sose használjunk blokkolt erőforrásokat ExecutionContextek használatakor, ha mégis szükségünk van rá, akkor inkább konfiguráljunk menetirányítókat típusonként (na jó ezt senki sem érteni :) "Configure dispatcher per type"). Mivel az Akka tud ilyet, és a Playben van beépített Akka, adja magát, hogy azt érdemes használni.
Kiemelte azt is, hogy nagyon nagyon nagyon nagy weboldalaknál lehet teljesítmény-növekedést elérni, ha a routingot több különálló fájlba tesszük, és egy custom router megírásával csak a megfelelő fájlt dolgozzuk fel, amikor a kérés beérkezik. A gyorsulás oka, hogy a  Play regexpeket illeszt az URI-re, és ha több száz illesztést kell csinálnia, mire a route fájl végén megtalálja a keresett bejegyzést, az felesleges processzor-terhelést jelent. A futtatott benchmarkoknál pár százalékos teljesítmény-növekedés volt mérhető.


A teljesítmény optimalizációs témaköröket elhagyva a következő előadást ismét  Julien Tournay és Pascal Voitot tartotta, és témája a Scalaz-Stream volt. A prezentáció lényege pár mondatban összegfoglalva annyi, hogy miként lehet streamek fogadására lecserélni a jelenleg is működő Iteratee megoldást Playben erre, az egyébként még fejlesztés alatt álló, megoldásra. A prezentációban egyetemi előadásokra emlékeztető részletességgel  mesélték el többek között, hogy miért is jó ez az újfajta megközelítés, hogy milyen problémákba ütközhetünk a HTTP, és a WebSocket eltérősége miatt, stb.


Ebédszünet előtt még Adam Evans és Asher Glynn tartott egy (számomra nem annyira érdekes) előadást arról, hogy milyen tapasztalataik voltak, amikor a BBC gyerekeknek szóló PHP-s megoldását lecserélték Play frameworkre. Architekturálisan volt egy PHP frontendjük, ami a BBC Java-s backendjéhez fordult adatokért. Ami miatt a Playre esett a választásuk:
  • Mert Scala, ezt nem is fejtették ki bővebben
  • Full stack web-framework
  • Reaktív, könnyű benne non blocking szolgáltatásokat készíteni és hívni
  • Typed safe template rendszer eléggé awesome
  • Sok vállalat használja, és zizeg az egész
Miután kiválasztották a keretrendszert csináltak egy pilot projektet, ami nagyon jól sikerült, a PHP fejlesztőknek könnyű volt átszokni Scalára, és mindenki boldog.


A sponsor pitch után következő előadás igazán érdekes volt. Grant Klopper a The Guardian hírportál frontendjét kiszolgáló alkalmazásról beszélt, milyen problémákkal kell szembenézniük, milyen megoldásaik vannak, stb. Elmesélte, hogy naponta három Budapestnyi ember látogatja meg a weboldalukat, átlagosan mindegyik megnéz három oldalt. 900 kérést kell kiszolgálniuk minden másodpercben. A rendszer teljesen nyílt forrású, és elérhető a githubon, sőt nem csak elérhető, hanem konkrétan onnan buildelik a live rendszert. A teljes frontend letölthető egyetlen futtatható jar fájlként, és elindítható a java -jar frontend-artifact.jar paranccsal. A rengeteg kérés miatt mindent cachelnek, és a rendszer garantálja, hogy nem töltődik be sohasem ugyanaz a tartalom kétszer, a második kérés mindenféleképpen cache hit lesz. Beszélt továbbá még a deployment folyamatukról is, ami abból áll, hogy elindítanak három új verziót, regisztrálják őket a load balancerbe, majd lekapcsolják a régi hármat. Előadás után külön odamentem hozzá, és rákérdeztem miként kezelik azt a szituációt, amikor az összes node fut, de valami kódváltozás miatt a cachelt elem is megváltozik, és a régi verzióknak még a régit kell kiszolgálniuk, az újaknak pedig már az újakat. Dolgozom elosztott szinkron cachel, és ez nálunk bizony okoz problémát és fejfájást, éppen ezért mi kikapcsoljuk a cachet egészen addig, amíg minden nodeon az új verziójú szoftver nem fut. A válasz egész egyszerűen annyi volt, hogy sehogy. Azt mondta, hogy olyan rövid ideig fordulhat elő ez az eset, és a magas kérésszám miatt a statisztikában meg sem jeleni az a pár hibás kérés.


Utolsó előtti előadást Tobias Neef tartotta a kontrollerek absztraktálásának lehetőségeiről a Playben, hogy a lehető legjobban elkerülhessük a kódismétlést. Pár pontban összeszedve a lényeg:

A konferencia Johan Andrén előadásával zárult, aki a különböző aszinkron lehetőségekről beszélt Scala és Java platformokon. Először a problémára világított rá Johan, miszerint az általunk írt kód a kiszolgálási idő nagyon kicsi részében van végrehajtási fázisban, és az idő nagy részét különböző erőforrásokra való várakozással tölti. Tegyük fel, hogy ha van 20 adatbázis, és 200 kiszolgáló szálunk, akkor a 200 + 1-edik adatbázist nem használó szál nem lesz kiszolgálva. A problémára háromféle megoldást mutatott be.
Az első, használjuk bátran a Future és Promise osztályokat (részletes információt itt találsz). Hátrányának azt nevezte meg, hogy amikor több rétegen keresztül dobáljuk a Future osztályokat, a rétegek között állandó jelleggel konvertálni kell azokat. Például egyik szerviz visszatér a userek listájával, de a hívónak JSON listát kell visszaadni, amit az őt hívónak HTML kóddá kell alakítani, stb. stb. Javaslata szerint az alábbi esetekben érdemes ezt a megoldás választanunk:
  • Amikor más szolgáltatásokkal kommunikálunk
  • Párhuzamos végrehajtásra, amikor a szálak teljesen elkülöníthetők egymástól
  • Egyszerű háttérszolgáltatások implementálásakor
Utóbbi esetre nem tenném a nyakam, Tomcat esetén mivel a kiszolgáló szál referenciát őriz a Future objektumra szépen bevárja annak végeredményét, szóval én nem javaslom ezt a kombinációt.
Következő lehetőségként az Akka Actorsokat vetette fel, segítségével esemény vezérelt programozást tudunk megvalósítani. Az eljárás lényege, hogy van egy bemenő sor, amibe be tudják a kiszolgáló-szálak küldeni a kéréseket, és a rendszer egy szálon szépen sorban végrehajtja azokat. A technika nem sebességéről híres, blokkolja a többi futó szálat, de cserébe jól skálázható. Mikor használjuk:
  • Amikor állapotokra van szükségünk
  • Adat streamelés
  • Esemény vezérelt programozásra
  • Háttérfolyamatok végzésére
Utolsó lehetősségként a Iteratee-k használatát mutatta be. Sajnos ennek nincs Java-s megfelelője, a Chunks API tud valami hasonlót, de képességei messze elmaradnak a Scala natív megoldásától. A dolog lényege, hogy pici szeletekben adható át a feldolgozásnak az adat (Enumerators), és a rendszer lényegében reagál az adatra, és várja a következő darabkát.
  • Enumerator[A] -> Iteratee[B, R]
  • Enumerator[A] -> Enumeratee[A, B] -> Iteratee[C, R]
Lényeges külömbség a hagyományos feldolgozással szemben, hogy az első hibás darabka esetén már lehet kezelni a hibát, nem kell a teljes adatfolyamot megvárni.
Mikor használjuk:
  • Streamel adatok esetén

A konferencia végére igencsak megfogyatkozott a létszám, láthatóan mindenki kellőképpen elfáradt. Meg kell hagyni, hogy elég tartalmasak voltak az előadások, szóval le a kalappal a szervezők előtt, nem bízták a véletlenre a mentális zombiságunkat. Gratula ezúton is.

2014. január 16., csütörtök

Ping-conf Day 1

Volt szerencsém a napomat a Ping-conferencián tölteni. A 2 napos rendezvény fő témája a Play Framework, mely a legelterjedtebb, hanem az egyetlen Scala nyelvre épülő webes keretrendszer. A rendezvény bár nem volt olcsó, mégis elég nagy tömeget mozgatott meg, hogy őszinte legyek sokkal kevesebb magyar résztvevőre számítottam. A konferencia, mely minden igényt kielégítő módon volt megszervezve, népszerűsége láttán elgondolkoztam, hogy a JUMon is több Scalás előadással készülhetnénk, mert az itt látottak alapján komoly érdeklődés van a Scála iránt Magyarországon.


Az első előadást Sadek Drobi tartotta, és a funkcionális programozás alapjairól beszélt, betekintést nyújtva példákon keresztül a composability szépségeibe. Röviden: minden funkciónak kötelezően van visszatérési értéke, és ezekkel a funkciókkal könnyedén lehet hívási láncokat alkotni. Az előadás végén elhangzott egy kérdés, miszerint mennyivel jobb megoldás ez, mint a Java 8-cal érkező Lambda kifejezések? A válasz tömör volt és egyszerű, mivel a Java hemzseg a void metódusoktól, igazán sosem lesz benne az az erő, amit a Scala nyújtani képes.


A második prezentációt Matt Hamer tartotta, és témának a többszörös JVM architektúrákat választotta. Számomra ez volt az egyik legérdekesebb előadás. Matt elmesélte, hogy az alkalmazás, amit fejlesztenek több részre van felosztva, egy prezentációs rétegre, ahol Play biztosítja a kiszolgálást, és n+1 business rétegre -szintén Play-, ami értelem szerűen backendként funkcionál a prezentációs rétegnek, és egyéb klienseknek. A szeparáció elsődleges okainak a következőket nevezte meg:
  • Könnyebb Load balancing
  • Izoláció a rétegek között
  • Különböző gyakorisággal történik a deployment
Miután ismertette az architektúrát, Matt részletesen kifejtette, a két réteg közötti kommunikációs lehetőségeket. Először számba vette a JSON küldözgetést, de ez két JVM között nem a legideálisabb választás, ezért áttért Java közelibb megoldásokra.
  • java.io.Serialize, mint az köztudott érzékeny a verziózásra, egyáltalán nem rugalmas
  • Protocol Buffer, sokkal jobb választás, de a konfigurációs állományokat állandóan karba kell tartani
  • Akka custom serialization, ez a megoldás bizonyult a legjobbnak, orvosolja a kompatibilitási problémákat, nem kell konfigurációs állományokat napra készen tartani, és nem utolsó sorban a Playben van beépített Akka

Egy rövid szünetet követően a harmadik téma következett "Build reactive apps on the JVM" címmel. Christopher Hunt saját fejlesztésű keretrendszerüket mutatta be. Megmondom őszintén nem tudtam teljesen elképzelni a felépítését, de nagy vonalakban egy Play frameworkre épülő, Rhino enginet használó JavaScript, igen jól olvastad JavaScript webframeworkről volt szó. A rendszerhez már most is elérhető néhány plugin (CoffeeScript, JS Hint, Less, Require.js, Jasmine). Ez jutott eszembe.

A következő, szám szerint a negyedik prezentáció Yann Simon nevéhez fűződik, témája pedig a Cake Patter In Scala gyakorlati bemutatása volt. A példa alkalmazás 3 részre volt tagolva, 1 frontend, és 2 backend modulból állt. Az első verzióban a frontend modulba be voltak égetve a backend modul szervizei, és a hívások. A megoldással a legnagyobb probléma, hogy a teszt-piramis (unit, component, integration) valójában nem is piramis alakú, hiszen kis számú unit teszten kívül lényegében mindent az integration teszt szintjén kell vizsgálni. A probléma felismerését követően elkezdte átalakítani az egyes rétegeket, amíg el nem jutott arra a szintre, hogy mindent a Scala Component based Dependency Injectionnel oldott meg. Én valahol a folyamat közepén vesztettem el a fonalat, mikor elszaporodtak a trait x extends y with z, meg class a extends b self: c kifejezések. Mindentől függetlenül 2 fontos dolgot leszűrtem:
  • Ez a megoldás annyival jobb más DI megoldásoknál (Spring, Guice), hogy már fordítási időben kiderül ha valami nem stimmel, és nem csak futás időben jönnek elő a problémák
  • Nem érdemes kimaxolni ezt a megoldást, mert az egyszerű példa esetén is 20 trait "osztályra" volt szükség, inkább az elégséges szinten érdemes megállni

Az ebéd szünetet követően Julien Tournay és Pascal Voitot tartott egy érdekes és egyben vicces szösszenetet "Typesafing your blobs" címmel. Hogy pontosan elénk tárhassák a problémát egészen az ősrobbanásig mentek vissza, majd az utazás a dinoszauruszok korán át egészen napjainkig tartott. Történelmi kalandozásuk során megálltak egy pillanatra a modern korba, amikor az emberek kísérletet tettek egy egységes protokoll kidolgozására, hogy a különböző informatikai rendszerek tudjanak egymással kommunikálni, és megszületett a SOAP. Folytatásként arról beszéltek, hogy a technológia fejlődésével egyre inkább ki vannak téve a webes szolgáltatások egymásnak és pontosan ezért egyre fontosabb, hogy a külső forrásokból érkező adatokat rugalmasan tudjuk kezelni és ellenőrizni. Példának hozták fel, hogy 2 éve a WebSoket még csak Sky-fi volt, ma pedig már szolgáltatások épülnek rá, utalva ezzel, hogy milyen rohamosan fejlődik a világ. A fent említett dolgok vezették rá a srácokat, hogy egy teljesen új, a régivel kompatibilis, de a mai kor elvárásainak megfelelő (vagy inkább jövőbe mutató) validációs API-t fejlesztettek a Playhez. Természetesen teljesen funkcionális szemléletben készült a kód, kihasználja a Scala macrokban rejlő lehetőségeket, és a case classoknak valamint az implicit deklarációnak köszönhetően marshallerek, és konfigurációk nélkül képes egy JSON stinget Scala osztállyá alakítani. Az API másik erőssége, hogy POST-olt adatok ellenőrzésére is alkalmas kvázi módosítás nélkül. A fejlesztés még nagyban folyik, nem production ready a cucc, de akit érdekel itt megtalálja a projektet.

Az utolsó előtti előadást Matthias Nehlsen tartotta, és reaktív alkalmazás-fejlesztésről beszélt Play, Scala.js, és ReactJs segítségével. Engem főleg a ReactJs fogott meg, régóta keresek valami hasonló megoldást, de az AngularJs, Ember.js, KnockOut.js eddig nem nagyon jött be. A ReactJs-nek nagyon rövid a tanulási görbéje, és a teljesítménye is kiemelkedő, köszönhető annak, hogy a valós DOM fa mellett van belül egy virtuális DOM, és a DOM manipuláció után csak a diff-et szinkronizálja a valós fával. Érdekes volt látni, hogy milyen egyszerűen kötötte össze a nézeteket renderelő ReactJst a Scalaból generált JavaScript kódot, és a Scala.js-t a Play-el Mathias, lényegében minden Scalában volt megírva, volt pár action binding JavaScriptben, és némi HTML-nek látszó ReactJs template.

Utoljára maradt Nicolas Martignole esettanulmánya, melyben két általa fejlesztett projekt sikertörténetét tárta a nagyérdemű elé. Webes rétegnek Play2-t választotta, adattárolásra és cachelésre pedig Redist. Láthatóan ő boldog volt a megoldással :).

Ennyi történt az első napon, igyekszem holnap is megjelenni/jegyzetelni. Folytatás...