B R I L L I E N
P L A T F O R M
do k ume ntá ció
FELHASZNÁLÓI KÉZIKÖNYV írta: Fazekas Imre
R e f e r e n c i a v e r z i ó : 1 0 . 0 5 . 0 2 • E m a i l : i n f o @ b r i l l i e n . o r g • We b p a g e : w w w. b r i l l i e n . o r g
F e l h a s z n á l ó i k é z i k ö n y v!
Köszönetnyilvánítás Köszönetemet fejezném ki minden személynek kik közreműködtek a könyv elkészítésében: - Zámbori Zoltánnak és Juhász Istvánnak, hogy időt szenteltek a koncepció és a modell átgondolására. - Kollégáimnak: Orosz Szilárdnak és Szegedi Lászlónak, akik elvállalták egyes fejezetek fordítását és a fordított anyag nyelvi helyességének ellenőrzését. - Kollégáimnak: Lajtos Máténak és Csukás Leventének értékes gondolataikért
Copyright © 20010 Wit-In Comp Inc. Minden jog fenntartva.
A GYÁRTÓ A SZOFTVERT A "LÁTOTT ÁLLAPOT" SZERINT BIZTOSÍTJA, MINDENFÉLE JÓTÁLLÁS NYÚJTÁSA NÉLKÜL. A VONATKOZÓ JOGBAN MEGENGEDETT TELJES MÉRTÉKIG KIFEJEZETTEN ELUTASÍTUNK MINDEN - KIFEJEZETT VAGY REJTETT - JÓTÁLLÁST, A TELJESSÉG IGÉNYE NÉLKÜL, BELEÉRTVE AZ ELADHATÓSÁGRA, EGY ADOTT CÉL SZERINTI ALKALMASSÁGRA, VAGY VALAMELY HARMADIK FÉL JOGAINAK SÉRTETLENSÉGÉRE VONATKOZÓ JÓTÁLLÁSOKAT. A JELEN KÖZLEMÉNYBEN SZEREPLŐ SZERZŐI JOG TULAJDONOSA VAGY TULAJDONOSAI SEMMILYEN ESETBEN NEM FELELŐSEK SEMMILYEN KÜLÖNLEGES, KÖZVETLEN, KÖZVETETT, VAGY KÖVETKEZMÉNYES KÁR ESETÉN (BELEÉRTVE EBBE, DE NEM LESZŰKÍTVE A HELYETTESÍTŐ TERMÉKEK VAGY SZOLGÁLTATÁSOK BESZERZÉSÉBŐL, HASZNÁLATKIESÉSBŐL, ADATVESZTÉSBŐL, VAGY ELMARADT HASZONBÓL; VAGY ÜZLETI FORGALOM CSÖKKENÉSÉBŐL ADÓDÓ KÁROKAT), BÁRMI OKOZTA IS AZT ÉS BÁRMILYEN FELELŐSSÉGI ELMÉLET ALAPJÁN, LEGYEN AZ SZERZŐDÉSES, SZIGORÚ FELELŐSSÉG, VAGY SÉRELEM (BELEÉRTVE A GONDATLANSÁGOT ÉS EGYEBEKET), AMELY JELEN SZOFTVER HASZNÁLATÁBÓL ERED, MÉG ABBAN AZ ESETBEN IS, HA A KÁR BEKÖVETKEZÉSÉNEK LEHETŐSÉGÉRŐL ÉRTESÍTÉST KAPOTT.
Debrecen, 2010 március F e l h a s z n á l ó i k é z i k ö n y v!
Tartalomjegyzék
Motiváció!
3
A Powerobject modell!
5
Presence!
5
Unit!
6
Context!
7
Flow!
8
Alapok!
10
Protokollok és formátumok!
10
A Clarity modul!
11
Az Ignition modul!
13
A Core modul!
13
A Services modul!
13
Elérhetőség!
13
Első lépések!
14
Deployment!
16
Deployment csomag!
16
Library és Resource management!
17
Presence loading!
17
Naplózás!
18
Flow management!
18
F e l h a s z n á l ó i k é z i k ö n y v!
Ta r t a l o m j e g y z é k
i
Modellezés és fejlesztés!
19
Tutorial 0: Hello!
20
Tutorial 1: A kliens!
24
Tutorial 2: A szerver oldal!
25
Tutorial 3: A Flow és a példányosítás menedzselése!
27
Tutorial 4: Időbeli távolságok kezelése!
31
Tutorial 5: Ügyfél adatbázis bevezetése!
36
Tutorial 6: Ajax-alapú prezentációs réteg fejlesztése!
43
Tutorial 7: Servlet integráció!
46
Tutorial 8: Rendszerfolyamatok kezelése!
51
Tutorial 9: Exception handling!
52
1. Rossz típusú input!
52
2. Nem megfelelő tartalommal rendelkező input!
53
3. Belső hiba!
54
4. Timeout-ok kezelése!
55
Tutorial 10: A Processor presence!
56
Tutorial 11: Környezetek használata!
59
F e l h a s z n á l ó i k é z i k ö n y v!
Ta r t a l o m j e g y z é k
ii
Motiváció
!
Valahol minden eszköz célja, hogy életünket és a benne megjelenő problémáink kezelését egyszerűsítse, segítse.
Nem illene, hogy kivétel legyen ez alól az IT sem, így a rengeteg vállalati megoldás az üzleti élet kihívásait hivatott kezelni. Ha közelebbről megvizsgáljuk a valóságot, hogyan épül fel, hogyan működik, nem nehéz észrevenni annak erős strukturáltságát, hierarchikusságát. Ahogy elképzelünk egy folyamatot, felépítünk egyfajta belső modellt, képet a dologról. Ez egyszerűnek és átláthatónak látszik ami a megvalósításhoz közeledve bonyolulttá, nehezen átláthatóvá és megalkuvásokkal telivé válik. Ennek a legbelsőbb oka az absztrakciós és modellezési eszközök fejletlensége vagy éppen gyatrasága. Tényleg nehéz és fáradtságos munka az erősen strukturált modellt egy tepsibe leképezni. A valóságot mindig folyamatok, kommunikációk halmazaként látjuk, mely erősen strukturált. Például egy bank rendszer esetén kezelnünk kell átutalási, számlavezetési, felügyeleti stb. szolgáltatásokat. Ahogy egy ilyen rendszert magunk elé képzelünk, folyamatokat, részfolyamatokat, kommunikációkat látunk. A probléma ábrázolásához olyan modellezési eszközökkel kellene rendelkezni mint például a folyamat vagy környezet. Az asztali alkalmazások területéről a vállalati szintre lépve jóval komplexebb és strukturáltabb problémákkal szembesülünk, melyet az IT megoldások absztrakcióban és modellben nem követnek. Ez rögtön bonyolultsághoz, szinte kibogozhatatlan struktúrákhoz és rengeteg munkához vezet. Gondoljunk csak a fent említett banki rendszerre, ahol a folyamatok modellezésénél vissza kell vezetnünk minden kommunikációt és többszintű struktúránk elemeit elemi objektumok szintjére. Hiába kell nagyobb és komplexebb rendszereket megtervezni, modell szinten nem áll rendelkezésünkre a feladattal jobban harmonizáló eszköz. Nem tudjuk megfogni modellezésileg a folyamat, vagy a “kiemelt ügyfelek” mint környezet vagy a kimutatások (halmazműveletek) fogalmát. Röviden, az általunk elképzelt modell vagyis belső kép és a tényleges működő rendszer között óriási a távolság. Az OO paradigma legfontosabb célkitűzése egy valósághoz közeli modell megalkotása, jelentősen egyszerűsítve a szoftveres reprezentáció megalkotását. Sajnos fogalmainak, absztrakciós eszközeinek fejlődése megrekedt egy szinten, mely még mindig nagyon távol esik az eredetileg kitűzött céltól. Gondoljunk csak a JavaSE-re. Rengeteg fantasztikus fogalmat definiál: osztály, objektum, csomag, absztrakt osztály, interfész, generikus, stb, kiváló alapot nyújtva egy strukturált modell számára. Az SE fogalmainak magasabb szintre emelése kellett volna, hogy képezze egy vállalati problémák megoldására készített EE csomag gerincét. A JavaEE ezt kellett volna, hogy megvalósítsa, de nem lett több mint egy SE köré épített library gyűjtemény. Sőt, a thread safety, stateless design, tranzakciókezelés oltárán sok modellezési eszközt fel kellett áldozni, jelentősen redukálva az elérhető OO megoldások és technikák körét (design pattern-ek egy része, generikusok, stb.). Ha belevesszük, hogy például a klaszterezés, mint követelmény felmerülésével milyen modellezési ajánlásokat kell figyelembe venni, az SE eszközkészletének jó részének búcsút inthetünk. Kicsit gyakorlatiasabb probléma a vállalati platformok nagyfokú komplexitása. Elég egy echo “szolgáltatást” nyújtó EJB-t publikálnunk, hogy megtudjuk mekkora fekete doboz teljesít szolgálatot az háttérben. Ez addig nem gond, míg a fejlesztés simán, gördülékenyen megy és varázslókon keresztül lehet a programokat összekattogtatni, és problémáink mintaszerűen illeszkednek az oktatóanyagok feladataihoz.
!
A Brillien megalkotásánál a cél egy egyszerű, magas modellezési absztrakciós szinttel rendelkező platform
megalkotása volt. Egy letisztult vállalati platform, mely új modellezési eszközöket nyújt, pár lépéssel csökkentve a belső kép és a működő rendszer közötti távolságot.
F e l h a s z n á l ó i k é z i k ö n y v!
Motiváció
3
A Brillien alapelveit 3 jellemző képviseli: - szabadság: mely megjelenik a megvalósított rendszer modelljében, az entitások kommunikációjában, a cserélt adatokban. Az alkalmazásunk logikai és kommunikációs hálóját, a folyamatait, entitásait és klienseit belső modellünknek megfelelően alakíthatjuk ki. - egyszerűség: minden eszköznek, amit egy probléma kezelésére készítenek, a legfontosabb alapelve, hogy a probléma megoldását a végletekig egyszerűsítse. Ennek megfelelően egy vállalati platform esetén ez magában foglalja a tervezés, fejlesztés, bevezetés és betanulás jelentette feladatok lehető leghatékonyabb minimalizálását. A Brillien minden fogalmában és működésében egyszerűséget mutat, hogy a végletekig egyszerűsödhessenek a komplex vállalati problémák. - absztraktság: a Brillien célja, hogy új fogalmakat, struktúrákat, műveleti egységeket definiálva segítse a vállalati problémákra adott megoldások tervezését és megvalósítását, leváltva a szinte teljesen sík modelleket és alacsony szintű absztrakciós eszközöket.
!
Ez a könyv bemutatja, hogyan épül fel és működik a Brillien, hogyan lehet az üzleti problémákat lemodellezni
és megoldást adni rájuk. Fontos kiemelni, hogy ismert dolgokra új fogalmak kerülhetnek bevezetésre, vagy esetenként a szóhasználat, a definíciók eltérhetnek a megszokottól. Ennek elsődleges célja, hogy a Brillien leírása minél harmonikusabb és kifejezőbb lehessen. Az egyértelműség jegyében minden kritikus pontnál magyarázat fogja segíteni az olvasót. Az írás kicsit szabad szellemű szerkesztésmódot és egyszerű szemantikát hordoz, mégis ez közelíti meg legjobban azt, ahogy a Brillien tényleg működik: egyszerűen. Ezen könyv erősen támaszkodik Java alapfogalmakra és eszközökre, így némi Java ismeret ajánlott.
F e l h a s z n á l ó i k é z i k ö n y v!
Motiváció
4
A Powerobject modell ! !
A Powerobject egy halmaz-elvű kommunikációs modell, melynek célja, hogy a meglévő vállalati OO modellhez
képest egy olyan absztrakciós szintet biztosítson, amivel a valós világ modellezni kívánt részét egyszerűen lehet ábrázolni. A könnyű megértéséhez a halmazelmélet és az OO fogalmainak ismerete szükséges.
Presence !
A powerobject modell legalapvetőbb fogalma, mely egy önállóan kommunikálni képes, egyedi névvel
rendelkező objektumot jelöl. A kommunikáció nem más, mint egy entitástól egy másik fele áramló üzenet és ahhoz társított adatok halmaza. Míg egy objektum egy adat-, és eljárásmodellt magában foglaló egységet jelent, addig a presence egy kommunikációs egység, mely semmi mást nem csinál csak kommunikációkat ad/fogad, és a “kommunikációs térben” képvisel valamilyen állapoto(ka)t. Ez három különböző szempont vagy nézőpont egysége minden objektumra nézve. A presence ezen három szempont egységbe zárása, nevezhetjük entitásnak is. A powerobject modellben aszinkron kommunikáció biztosítja, hogy az entitások szabadon, várakozás nélkül kommunikálhassanak egymással. A szinkron, várakozást igénylő hívások egyszerű aszinkron hívásokkal valósulnak meg. Egy entitás által indított kommunikáció válasz kommunikációt is indukálhat, de nem szabja meg, hogy a válasz szükségképpen a feladóhoz kerüljön vissza. Erősen csorbulna a kommunikáció szabadságra vonatkozó követelménye, ha nem lehetne delegációt végrehajtani. A szabadságból, mint feltételből következik a kommunikáció elutasításának lehetősége is, melyről a kezdeményező entitás értesül. A kommunikáció egy üzenetből, és opcionálisan, ahhoz tartozó adatokból, paraméterekből áll. A küldött adatok felépítésére vonatkozólag nincsenek megszorítások. Az entitások működésük során mindig valamilyen állapoto(ka)t képviselnek. Egy Tanuló objektum tanulás állapotban van ha olvas, matematikai feladatokat old meg vagy valamilyen kísérletet végez, vagy egy autó “használaton kívül” állapotban van, ha nem működőképes vagy nincs forgalmi engedélye vagy kiállításra került. Vagyis: egy állapot egy kommunikációs szempont absztrakt leírása, egy metainfo, egy tag, ha úgy tetszik. Így nem tévesztendő össze az OO állapotfogalmával ami ennél jóval konkrétabb hiszen “adattagok értékének összessége”-ként került definiálásra. Röviden, a presence-ek állapotai a kommunikációkészség absztrakt leíró tulajdonságai. Például egy tanulás állapotában lévő Tanuló nem fog kommunikációkat fogadni, vagy adni; míg egy “használaton kívüli” autó nem fog gyújtást adni a motorra és nem fog forgalomba állni. Egy entitás akárhány állapotot felvehet, dinamikusan változtathatja állapotait. Így tekinthető egy állapotautomatának is. Egy állapot felvétele, megváltoztatása is kommunikáció, mégpedig egy entitás belső kommunikációja. Ez a definíció lényeges, hiszen ebből egyértelműen következik, hogy egy entitás elutasíthatja saját állapotváltozását mint kommunikációt, ezáltal képes saját állapotterét konzisztens állapotban tartani.
F e l h a s z n á l ó i k é z i k ö n y v!
A Powerobject modell
5
Unit !
Speciális presence típus, mely olyan entitásokat jelöl, melyek képesek más entitásokat magukba foglalni,
röviden halmazok. Halmaznak tekintett minden olyan presence, mely más presence-eket egy új entitás egységébe zár. Ez a definíció egyszerűnek tűnik, mégis sok következmény figyelhető meg. Onnan nézve, hogy képes más entitásokat tartalmazni, adódnak a halmazműveletek, mint kommunikációk: a részhalmazképzés, unió, metszet, tartalmazás, ekvivalenciareláció szerinti osztályozás, stb. Ezen kommunikációkra - sőt minden powerobject modellben megjelenőre igaz a szabadság mint feltétel, ami jelentheti azt, hogy egy halmaz elutasít egy tartalmazásra vonatkozó kommunikációt. Ez valójában a karakterisztikus függvény lényege, definiálni, hogy mely elemek képezhetik részét ennek az egésznek. Természetesen elemként Unit-ok is megjelenhetnek, ami részhalmazképzést jelent. Vagy egy Unit mely - a karakterisztikus függvényének megfelelő - részhalmazokat foglal egy egységbe, a tartalmazott entitások felöl nézve, unió-képzést jelöl. Minden alapszintű matematikai halmazművelet könnyen lemodellezhető. Kiemelném, hogy a kommunikáció-orientáltság mennyire lényeges. Vegyük azt a kommunikációt, hogy “minden olyan áru, melynek szavatossága egy héten belül lejár, érték-csökkentetté válik.“ Sem a nyelvtani, sem a matematikai leírás nem véletlen. Nem arról van szó, hogy fogok minden egyes, adott feltételt kielégítő árut, és mindegyik felé indítok külön-külön egy-egy kommunikációt, hanem az adott feltételt kielégítő áruk halmazát nevezem meg. Mondhatnánk, hogy mi a különbség ha a végeredmény megegyezik azzal, amikor a kód lefut és végső soron mindegyik áru egy-egy üzenetet kap, de modellezésileg a kettő nagyon nem ugyanaz. Nem véletlenül létezik levelezési lista sem. Nagyon nem ugyanaz egy címre írni egy levelet tudván, hogy sok ezren meg fogják kapni, vagy sok ezer emailt megcímezni és feladni. Akárhogy is, egy halmaz kaphat olyan kommunikációt, mely nem neki, mint entitásnak, hanem a tartalmazott entitások összességének szól. Aprónak látszó különbség, a gyakorlatban mégis jelentőséggel bír, hogy lehetőség van sok entitás egy egységként való kezelésére, ahol bármely beérkező kommunikáció természetes módon jelenik meg a tartalmazott entitások és részhalmazok esetén azok entitásainak szintjén. Valahol a kollekcióknak kellett volna ilyenekké válniuk, sajnos modellezésileg nem lettek többek egyszerű tömbnél. !
A halmazorientáltság fontossága kiemelkedő, hiszen bármilyen valós példa, feladat leírható halmazelmélettel, a
belső modellünkhöz nagyon harmonikusan illeszkedő módon. Vegyük csak az a példát, amikor minden olyan osztályt, melynek van kitűnő tanulója, fejenként egy-egy szaloncukorral jutalmazzuk. Vagyis az a halmaz, mely azon osztályok mint halmazok uniója, melyek tartalmaznak kitűnő hallgatót, kapnak egy halmazszintű kommunikációt: szaloncukrot mindenkinek.
F e l h a s z n á l ó i k é z i k ö n y v!
A Powerobject modell
6
Context !
Vizsgáljuk csak meg a halmazba való sorolást mint kommunikációt, melynek a végén a tartalmazás jellegű
viszony felépül. A halmazok az entitások szemszögéből nézve egy őket egy egységbe foglaló entitás, egy közös tér. Ez azt jelenti, hogy van egy finom szemantikai kapcsolat az entitások között, mely egyébként pontosan a karakterisztikus függvény által leírt feltételrendszer. A közös tér elnevezés jól szemlélteti, hogy ez a szemantikai kapcsolat azt jelenti, hogy az entitások “tudják” mely halmaz(ok)nak részei. !
Ez viszont nagyon érdekes egy rendszer kommunikációja szempontjából, hiszen egy entitás számára különbség
jelenhet meg, hogy egy kommunikációt egy halmazbeli társa vagy más halmaz(ok)ba tartozó entitások felé indít. Gondoljuk csak végig, mennyivel másképpen kommunikálunk a valós életben is baráti körben, mint egy bírósági tárgyaláson. És nemcsak a stilisztikai elvárások okán, hanem egyszerűen mert számít egy kommunikáció során, hogy az adott entitások egy közös térben helyezkednek-e el vagy sem. A valós életben mennyit használjuk a környezet szót egy valamilyen szempont szerint vett közös tér leírására. Nyelvi környezet, gazdasági környezet, stb. Vagyis a közös tér több mint egyszerű egységbe foglalás abból a szempontból, hogy viszonyulási pontot jelenthet az adott halmaz által tartalmazott entitások számára. Mennyivel másként értelmezzük az ajándék fogalmát, ha két barát vagy egy civil és egy rendőr között (megvesztegetés szinonimája) merül fel. Vagy mennyivel másként értelmezzük a “tegyétek hidegre” kifejezést ha étteremben vagy gengszterfilmben halljuk. De vizsgálhatjuk az egy környezeten belül lévő presence-ek kommunikációját is. Vegyünk egy egyetemi tanszéket példaként. A tanszékvezető személye egy adott tanszék minden oktatója, hallgatója számára ugyanazon személyt jelöli. Más tanszékek oktatói számára a “tanszékvezető” elnevezés más entitást fog jelölni, de adott tanszéken belül mindig egyértelmű kiről van szó. Vagyis a tanszék egy környezetet ír le, melyen belül a tanszékvezető, a titkár, stb. fogalmak mind-mind a környezet egy-egy hivatkozási illetve viszonyulási pontjai, megosztásai. Ennek megfelelően minden tanszéknek van saját tanszékvezetője, titkára, stb. mely minden környezetben más és más, de minden tanszéken létező fogalom. Amennyiben valamilyen kérelmet akarunk írni a tanszékvezetőnek, ezen nevet használjuk a címzésre is, hiszen valójában a kérelem nem az adott entitásnak magának, hanem a környezetben betöltött szerepének szól. Arról nem is beszélve, hogy a titulus mögötti konkrét presence időben változhat, de a környezeten belül a titulus időben állandó. !
Röviden, a környezet egy speciális halmaz, mely képes közös térként, közös viszonyulásként megjelenni az
entitások számára. Nevezhetném megosztásnak is, hiszen valójában erről van szó, analógiát mutatva a halmazelmélet osztály fogalmával.
F e l h a s z n á l ó i k é z i k ö n y v!
A Powerobject modell
7
Flow !
A fenti fogalmakkal presence-ek közötti kapcsolatokat lehet ábrázolni. Vajon van-e értelme a kommunikációk
közötti kapcsolatok ábrázolásának? Van-e halmazként, más kommunikációkat egységbe foglaló kommunikáció? Természetesen, sőt a valóságban meglehetősen gyakori. Gondoljuk csak végig, hogy egy bankban az ügyfél pénzt szeretne felvenni. Ekkor az ő kommunikációja elindul és csak akkor zárul le mikor a banki asszisztens átadja az összeget. De ez a kommunikáció struktúrát mutat, hiszen tartalmazza ezeket a kommunikációkat is: az ügyfél hitelesíti magát, a számláról levonódik az összeg, a pénz kikerül a pénztárból. Mitől több ez, mint egy metódus, mely más metódusokat hívogat? Nagyon sok mindenben. Egy utasítássorozat bárhonnan nézve is lineáris, egy dimenziós struktúra. A flow-k halmazelvűségük miatt, gráfba rendezhetőek a modellezendő folyamatok logikai hálójának megfelelően. A fenti definíciók fényében, számos következményre lehetünk figyelmesek. Bár a flow belülről nézve egy komplex kommunikációs sorozatot reprezentál, kintről nézve egy halmaz, egy egység vagyis atomi művelet. Bármely objektum, mely kommunikálni szeretne az adott flow entitással kintről látja azt, atomi műveletként szemléli. Elemi művelet lévén lefutása vagy sikeres vagy sikertelen. Ezt minden flow reprezentálja állapotterében. Gondoljuk csak végig a korábbi definíciókat! Például: - bármely entitás képes kommunikálni egy flow-val mint összetett kommunikációs entitással, így egy entitás képes megnézni hogy egy kommunikáció elindult-e, folyamatban van-e vagy lezárult-e. Képes kommunikálni vele, hogy módosítsa vagy leállítsa azt - flow entitások is képesek egymással kommunikálni, ami a folyamatok szinkronizációjaként interpretálható - a context fogalmából adódik, hogy egy ilyen kommunikációnak lehet állapottere, közös térként jelenhet meg más kommunikációk számára, stb. A flow nem más, mint egy olyan speciális context, mely struktúraként reprezentál egy adott kommunikációt. Ez azt jelenti, hogy környezetként írja le egy adott kommunikációnak a teljes folyamatát, a résztvevő entitásokat, köztük lévő kommunikációkat, környezetüket, minden paraméterüket egy egységbe foglalja és mint önálló entitást jeleníti meg más entitások számára. Sok következménnyel járó definíció ez, gondoljuk csak végig mennyire. !
Pár példa milyen erős modell-szintű absztrakciós eszköz is ez valójában: mivel a flow egy speciális context, így
halmaz is, így a flow-k természetes módon egymásba skatulyázhatóak. Egy flow, mely - mint atomi művelet - sikertelen lefutás esetén minden mellékhatását megszünteti, tranzakciónak tekinthető. Vagy egy flow karakterisztikus függvénye integritási megszorításként tekinthető valójában. Természetesen korlátlan sok struktúra kikombinálható belőle, óriási modellezési erőt biztosítva számunkra. F e l h a s z n á l ó i k é z i k ö n y v!
A Powerobject modell
8
Vegyük észre, hogy a flow definíciója nem tartalmaz korlátozást a kommunikáció időbeli távolságára vonatkozólag, vagyis a definíció megengedi és a valós élet meg is követeli, hogy egy folyamat egy adott feltételnek vagy körülménynek megfelelően megálljon és később új feltételek és körülmények megjelenésekor folytatódjon. Tipikusan az üzleti folyamatok külső forrásból érkező kommunikációjára való várakozása ilyen, ami jelenthet bankok közötti tranzakciókat, iktatórendszerenél igénylési folyamatok kezelését, stb.
!
!
A powerobject modell ezen 4 fogalmat vezeti be, melynek implementációja a Brillien-ben megtalálható. Ez a 4
fogalom képes modellszinten leírni a mai IT eszközök fogalmi rendszerét, ami nem igazán a powerobject modell érdeme, egyszerű halmazelmélet, semmit több. A fenti fogalmaknak megfelelően az alábbi definíciók kerülnek bevezetésre: Powerobject komponens: Presence-ek halmaza Powerobject alkalmazás: Flow presence-ek halmaza Powerobject rendszer: Powerobject alkalmazások és modulok környezete Megjegyzés: A fogalmak erősen és sokszorosan rekurzívak, így célszerű időt szánni a pontos és teljes megértésükre, hasznossá válhat a későbbi modellezések során. Természetesen látszik, hogy ezen funkciók darabokban, részlegesen és degradáltan, de fellelhetők a mai IT rendszerekben, bár sajnos nem mint absztrakciós eszközök, hanem mint sík modellt mutató segédfüggvények, utility könyvtárak.
F e l h a s z n á l ó i k é z i k ö n y v!
A Powerobject modell
9
Alapok !
Az előző fejezet áttekintésével az olvasó megismerhette a halmaz-orientált powerobject modell fogalmait és
működését, mely a Brillien alapjait képezik. A szerénynek mondható fogalmi rendszer kellően nagy absztrakciós teret biztosít számunkra, hogy egy egyszerű, de erőteljes modellel rendelkező vállalati platform születhessen. A modell megvalósítása során tiszteletben kell tartani a 3 alapelvet, melyre az egész felépült: - szabadság - egyszerűség - absztraktság !
Ezen alapelvek nemcsak a modellt, de az egész platform működését, használatát és üzemeltetését is jellemzik és
tükröződnek minden tervezési és implementációs döntésben. Célszerű szétválasztanunk egy platform megalkotásánál fellépő szükséges szinteket, feladatokat. A modell fogalmainak protokolloktól, könyvtáraktól, egyszóval bármi mástól független reprezentációját kell megvalósítani. A tényleges működést biztosító kódok megírása előtt egy futtató környezetre lenne szükség, mely alapszolgáltatásokat képes nyújtani bármilyen platform vagy rendszer számára. Az első lépésben előállt reprezentáció implementációja csak ezt követően állhat elő, mely az eggyel korábbi lépésben létrehozott futtatási környezetben kell hogy működjön. Célszerűen a platform maga is legalább két részre bontható: az alaprendszer, mely biztosítja a kitűzött célok megvalósítását és egy kiegészítő réteg, mely az elkészült alaprendszer funkcionalitását terjeszti ki. Összefoglalva, négy, rendre egymásra épülő csomag került definiálásra: - Clarity: a powerobject modell alapvető fogalmainak leírása Java nyelven. Ez többnyire interfészek és annotációk gyűjteménye. Ennek megfelelően nem definiál semmilyen konkrét protokollt vagy technológiát, csak alapműködéseket. - Ignition: egy Java-alapú platform működtetéséhez szükséges absztrakt eszközök modulja. Itt találhatóak - többek között - a konfigurációs állományokat kezelő eszközök, a saját osztálybetöltő, a kommunikációs protokollokkal kapcsolatos kódok. - Core: a Brillien implementációja, mely a platform működéséhez szükséges minden kódot tartalmazza, a clarity modul által definiált interfészek implementációival egyetemben. - Services: kiegészítő presence reprezentációk gyűjteménye, melyek nem képzik szerves részét egy powerobject implementációnak, de gyakorlati életben kiterjeszthetik annak funkcionalitását, alkalmazásának körét.
Protokollok és formátumok !
A Brillien-ben megjelenő kommunikációk kezelését az Ignition modul végzi, vagyis a presence-ek által
ténylegesen használt kommunikációs protokoll itt található. Ez megengedi, hogy adott esetben az Ignition kódja egy részének lecserélésével, a mostani implementációtól eltérő protokoll biztosítsa a kommunikációs réteget. Nem kis kutatás és vizsgálódás előzte meg az Ignition modul implementálását. A szabadság elv miatt garantálni kellett, hogy bármilyen entitás képes legyen kommunikálni a presence-ekkel, legyen szó böngészőben futó kódról, C kódokról, távoli natív kódokról, stb. Két különböző feladatra kellett megoldást találni: “transzport” protokoll és a protokoll felett szállított struktúra formátuma. Az entitások által küldött és fogadott üzenetek hordozhatóságát a JSON biztosítja, mely egy adatok cseréjére kifejlesztett egyszerű szöveges formátum. Számos következménnyel számolni kell:
F e l h a s z n á l ó i k é z i k ö n y v!
Alapok
10
- Komplex és saját típusok kezelése: mint minden interoperabilitást biztosító technológia, így a JSON esetén is erős korlátok közé esik az alkalmazható struktúrák halmaza. Az RPC-vel ellentétben, nem alkalmazható tetszőleges típus és formátum. Ez a JSON-ra különösen igaz, hiszen még a Web Services-nél is egyszerűbb struktúrát alkalmaz. - Contract kérdése: amennyiben egy egyszerű JSON-ban kerül leírásra az entitások közötti kommunikáció, akkor hol jelenik meg a contract szolgáltatás, mely a kód fázisában kikényszerítheti az ineteroperabilitást? Mi kényszeríti ki az interfészek közötti kompatibilitást? A JSON esetén maximum egy validációt biztosító eszköz képzelhető el, ami már futásidejű szolgáltatás lenne, ami jelentősen nagyobb fejlesztési illetve karbantartási költségeket generálna. A Brilliennek nincs contract-jellegű szolgáltatása, mert bár a kikényszerített interoperabilitás áldásos tud lenni, hatékonyabb modellezéssel és tervezéssel mégis megvalósítható a harmonikus kommunikáció és elkerülhetőek a kompatibilitási problémák. Ha úgy tetszik, a Brillien elvárja az alaposabb tervezést. !
A JSON üzenetek forgalmazására egy megfelelő “transzport” protokollt kellett találni, mely transzparens
módon biztosítja az üzenetküldést, garantálva a kézbesítést akkor is, ha pl. a címzett éppen nem elérhető. A számos bevált technológia közül az XMPP-re esett a választás, hogy a Brillien alatti transzportort biztosító réteg protokollja legyen. Így kommunikálni lehet Brillien-es entitásokkal weboldalról, natív kliensekből, de még akár egy IM alkalmazásból is. A Brillien minden kommunikációja XMPP alapú, de eszközöket biztosít web szolgáltatásokat vagy servlet-ek vagy egyéb rendszerekkel történő integrációra. Részleteket lásd a későbbi fejezetekben. Most tegyünk egy mélyebb betekintést a Brillien moduljaiba. A könnyebb megértést segítheti a forráskód és a javadoc dokumentáció megnyitása.
A Clarity modul !
Minden clarity module-beli Java fogalom a com.vii.brillien.kernel csomag része, mely kér részre tagolódik: axiom
és annotations alcsomagokra. Az első a powerobject fogalmainak Java reprezentációját, míg a második a Brillien-ben történő fejlesztéshez szükséges annotációkat tartalmazza. Ebben a részfejezetben csak az axiom csomag kerül ismertetésre, az annotations a tutorial részben kerül bemutatásra.
F e l h a s z n á l ó i k é z i k ö n y v!
Alapok
11
!
A powerobject modell presence fogalma, mely három kommunikációs szempont egységbe zárása, 4 Java
interfészként jelenik meg: Aspirer, Sensor, Progressor és Presence. Míg az első három interfész a presence fogalom által definiált 3 különböző kommunikációját írja le (rendre: indított, fogadott és belső), addig a Presence ezen interfészek al-interfészeként került ábrázolásra. A modul két alcsomagra bomlik: atomic és component. Az atomic definiálja a majdani implementációhoz szükséges legalapvetőbb interfészeket, mely magában foglalja a powerobject modellben leírt fogalmakat úgy mint Unit, Context, Flow, stb, valamint új fogalmak is bevezetésre kerültek: PresenceManager, Commander. Commander: Speciális flow, mely képes új presence típusok publikálására és kiiktatására, publikált entitások kezelésére, névszolgáltatás. Röviden ez a Brillien, mint platform menedzselési környezetének az interfésze. PresenceManager: Speciális Unit típus, mely egy adott presence típus példányainak kezelését segíti. Ide értendő a hivatkozott könyvtárak és eszközök ellenőrzése, példányosítás vezérlése, inicializálás elvégzése, stb. A component alcsomag a powerobject fogalmak interfészei által használt adatstruktúrák gyűjteménye. Itt találhatóak a következő interfészek (csak pár példa): Communication: A kommunikációt mint struktúrát leíró interfész. State: Az állapot absztrakt leírása. Az entitások állapottere ilyen objektumok gyűjteménye. StateDivision: Állapotátmenet-függvény reprezentáció. Validable: Egy kommunikáció paramétereként megjelenő információ validációt előíró interfész.
F e l h a s z n á l ó i k é z i k ö n y v!
Alapok
12
Az Ignition modul !
A com.vii.brillien.ignition csomag három fő csoportba sorolható osztályok gyűjteménye:
- Kommunikációs protokoll és kapcsolódó osztályok: XMPP-s segédosztályok az XMPP szabványnak megfelelően. - Brillien Classloader: Saját classloader típus, mely minden Brillien entitás és maga a Brillien betöltője is. - Segéd és szolgáltató osztályok: Reflection, string, i/o, konfigurációs file, object pool kezelést segítő osztályok (Megjegyzés: A forráskódokat tartalmazó SVN repository-ban ez a modul két részre lett felosztva. Ennek oka, hogy bizonyos service osztályok más projektekben is nélkülözhetetlenné váltak és biztosítani szeretném, hogy mások számára is hasznossá válhat még ha nem is kívánja a Brillien platformot bevezetni.)
A Core modul !
Ez a Brillien lelke, az alkalmazásszerver implementációja. Itt találhatóak a Brillien rendszer belső entitásai,
szolgáltatásai. A com.vii.brillien.core csomag két alcsomagra bomlik: - component: A powerobject modell fogalmi rendszerének teljes implementációja és a Brillien belső entitásainak gyűjteménye. - management: Segédosztályok, melyek alapszolgáltatásokat biztosítanak a Brillien szerver és a benne működő entitások számára. Például: adatbázis-kezelő szolgáltatások, alkalmazásszerver szintű konstansok, stb. A Services modul !
Kiegészítő presence típusok gyűjteménye, melyek valamilyen extra funkcióval hivatottak egy Brillien alapú
projekt funkcionalitását kiegészíteni. Néhány példa: - MessageQueue: Minden beérkező üzenetről értesíti az arra feliratkozott presence-eket - Timer: Időzítési funkciókat biztosít aszinkron kommunikációm keresztül
Elérhetőség A Brillien BSD licensszel rendelkező open source projekt, melynek: - forráskódja elérhető a https://brillien.dev.java.net/svn/brillien/trunk címen - bináris disztribúciója letölthető a projekt weboldaláról ( www.brillien.org ) - moduljai elérhetőek http://download.java.net/maven/2/ Maven repository-ban com.vii.brillien név alatt.
F e l h a s z n á l ó i k é z i k ö n y v!
Alapok
13
Első lépések
Helyezzük üzembe első Brillien szerverünket, mely a következő struktúrát mutatja:
!
Bár mind a forrás mind bináris disztribúció elérhető, hasznosabbnak tűnik a forrásból kiindulva felépítenünk
működő rendszerünket. Így első lépésként töltsük le a Brillien forrását az SVN repository-ból. A binárisok elkészítéséhez Maven2 build script áll rendelkezésre. A forrás letöltésével az alábbi könyvtárszerkezet jelenik meg:
- distributions : hivatalos bináris csomagok - source! !
- doc : a projekthez tartozó dokumentumok. Például ez a könyv, illetve a Maven build dokumentációja.
F e l h a s z n á l ó i k é z i k ö n y v!
Első lépések
14
!
- lib : felhasznált külső csomagok és eszközök, melyek nem találhatók meg más Maven repository-kban.
!
- res : a brillien alkalmazásához szükséges állományok, mint például: config file, dtd definíciók, stb.
!
- samples : példaprogramok, tutorialok
!
- src : Brillien forráskódja
A source/src könyvtárba lépve az mvn install utasítást végrehajtva lefordítódik a forráskódbázis és az mvn package assembly:assembly parancs segítségével a bináris disztribúció előáll a source/src/target/ alkönyvtárban egy zip formájában, melyet kicsomagolva bárhol, az operációs rendszertől függően a Starter.sh illetve Starter.bat parancsállománnyal indítható. !
Az elkészült Brillien disztribúció a következő struktúrát mutatja:
!
A platform állományszintű struktúrája nagyon egyszerűen képet mutat. Az activator könyvtár az entitások
publikálásának helye, míg a chieflib, deplib és lib a Brillien működéséhez szükséges programkönyvtárakat tartalmazza. A Brillien rendszer konfigurációs állománya a data/config/services.xml útvonalon, míg a naplózás alapértelmezett könyvtára a data/log útvonalon található. A gyökérben helyezkednek el a validációhoz szükséges dtd file-ok, valamint az indító script-ek. A services.xml fontosabb elemeit: - <serverName>, <serverVersion>: Brillien szerver egyedi neve és verziója - <xmpp>: Az xmpp szerver eléréséhez szükséges adatok - <jmx> : A Brillien JMX interfészen keresztül adatokat szolgáltat a rendszer belsejéről, például: memóriafogyasztás, thread pool, stb. - <debug> : Három lehetséges kimeneti csatorna áll rendelkezésre a logolás elvégzésére: konzol, socket, file. Ezek beállításait (logolás szintje, alapértelmezett logolási szint, stb.) lehet itt elvégezni. F e l h a s z n á l ó i k é z i k ö n y v!
Első lépések
15
Deployment !
A tervezés során, az implementálandó presence-eket valamilyen logika szerint (például: az alkalmazott
fejlesztési módszertannak/stratégiának megfelelően) modulokba rendezzük, hasonlóan mint bármely EE rendszer esetében. Minden modul egy jar állománnyá fordítódik, mely a Brillien publikációs/deployment egysége. Minden Brillien szerver a saját home könyvtárában lévő activator nevű könyvtárat monitorozza periodikusan. Új jar megjelenése, vagy régi megváltozása esetleg törlése indítja el a publikációs folyamatot. A változások függvényében presence-ek kerülnek publikálásra, lecserélésre vagy visszavonásra. Egy presence visszavonása nem vonja maga utána az éppen futó példányok, folyamatok leállítását. A jar állományok egy nevükkel megegyező könyvtárba kerülnek kitömörítésre, mely az adott modul home könyvtára lesz.
Deployment csomag
A jar állományok tartalma: - bin alkönyvtár : a modul által tartalmazott java kód class állományai. Kötelező. - lib alkönyvtár : a modul által használt library-k. Opcionális. - res alkönyvtár : a modul által használt erőforrások. Opcionális. - config/activate.xml : deployment descriptor. Opcionális. Egy modul akárhány presence definícióját és működésükhöz szükséges osztályt tartalmazhat. Az entitások működésük során felhasználhatnak más Java-alapú vagy JNI-kompliens programkönyvtárakat, vagy akár bináris kódokat. Ezek elhelyezése a lib könyvtárba történik. A res könyvtár felhasznált erőforrások gyűjtőhelye, ami jelenthet i18N szótárakat, konfigurációs állományokat, stb. A deployment descriptor vendor információkat, a presence-ek példányosításakor beállítandó inicializációs paramétereket és az úgy nevezett Activator osztály minősített nevét tartalmazhatja. Az Activator osztály feladata, hogy main metódusa végrehajtódjon a modul betöltését követően. Ez használható például modul-szintű inicializálások vagy valamilyen validációk elvégzésére.
F e l h a s z n á l ó i k é z i k ö n y v!
Első lépések
16
Library és Resource management !
A Brillien minden modul számára két lehetőséget biztosít programkönyvtárai illetve erőforrásai elérésére.
Globális szinten a Brillien disztribúcióban található lib és res könyvtárak, lokális szinten az adott modul saját lib és res könyvtárai biztosíthatják a hivatkozott tartalom tárolását. A globális könyvtárak tartalma minden modul számára elérhető. Az osztálybetöltők is követik ezt a felosztást. A Brillien egy saját betöltő hierarchiát épít fel, melynek gyökerében egy Brillien-specifikus classloader példány áll, mely a Brillien disztribúció deplib, lib könyvtáraiért felelős. Minden modul saját classloader példányt kap, mely az adott modul saját bin és lib könyvtárainak kezeléséért felelős és a Brillien fő osztálybetöltőjének gyermek eleme lesz. Az osztálybetöltődések során a modul saját classloader-e mindig elsőbbséget élvez a classloader hierarchiában fentebb lévőkhöz képest.
Presence loading !
A presence-ek publikálása két lépésben zajlik: betöltés és aktiválás. Első lépésként a talált jar állományok, más
néven Brillien archívok kitömörítése és a benne található presence osztályok betöltése történik. Ebben a fázisban a beépített PresenceLoader entitás minden talált class állományt betölt, hogy megvizsgálja Presence típusról van-e szó és a megtalált entitások publikációját elvégzi. Minden megtalált Brillien entitás publikálásra kerül automatikusan, éppen ezért nincs szükség deployment descriptor-ra a publikáció elvégzéséhez, csak az esetleges paraméterezéshez. Egy entitás sikeres publikációja esetén a Brillien a “Domo [entitásnév]” üzenetet naplózza. Bármilyen probléma felmerülése a modul publikációjának befejezését jelenti. F e l h a s z n á l ó i k é z i k ö n y v!
Első lépések
17
A második lépés a feldolgozott brillien archívokban talált entitások aktiválása. Ez jelentheti adott esetben a példányosításukat illetve az Activator osztály main metódusának végrehajtását, amennyiben ilyen létezik.
Naplózás !
A Brillien minden belső folyamatát, kommunikációját fontosságának megfelelő szinttel a konfigurációs
állományban meghatározott kimenetre naplózza. A naplózás szintjét attól függően célszerű megválasztani, hogy milyen részletes információ szükséges a működő rendszerben folyó kommunikációkról. A Brillien-ben minden entitás minden kommunikációja (küldött/fogadott/megválaszolt üzenetek, állapotváltás, stb.) automatikusan, programozási tevékenység nélkül naplózásra kerül. Tekintve, hogy a Brillien minden belső menedzselési feladatát is presence-ek végzik, így teljes működésük naplózásra kerül, ahol a log bejegyzés szintje attól függ, hogy az adott presence mennyire magas vagy alacsony szintű kommunikációkat folytat. A konfigurációs állományban megadott alapértelmezett naplózási szint azon entitások által naplózott információra vonatkozik, melyek nem specifikálják saját naplózási szintjüket.
Flow management !
Két alapértelmezett flow létezik a Brillien-ben: SUPREME_FLOW és SERVICES_FLOW. A rendszer indulásakor
a SUPREME_FLOW létrejön és elindul. Ez maga a Brillien alkalmazás szerver. Minden rendszerszintű presence (loader-ek, menedzserek, stb.) ezen folyamathoz társítódik. A SERVICES_FLOW a SUPREME_FLOW részfolyamataként jelenik meg, és szülőfolyamata minden futó Brillien alkalmazásnak. A Brillien alkalmazás definíció szerint folyamatok halmaza, így minden futó üzleti entitás eleme a SERVICES_FLOW-ból kiinduló fastruktúrának.
F e l h a s z n á l ó i k é z i k ö n y v!
Első lépések
18
Modellezés és fejlesztés !
Most, hogy a Brillien legalapvetőbb működési elveit áttekintettük, az olvasó egy példa bemutatásán keresztül
megismerheti a Brillien működését, eszközeit, a vele történő tervezés és fejlesztés alapjait. A példa, melyet lépésről lépésre építünk fel, egy egyszerüsített banki átutalás. A CreditBlue fantázianevű rendszer ügyfelek közötti átutalási szolgáltatást nyújt az alábbi use case-nek megfelelően:
!
A kliens egy átutalást kezdeményez bankja fele, mely a kérést feldolgozza és az átutalás sikerességéről értesíti
ügyfelét. Az átutalás címzettje más bank ügyfele lesz, így példaprojektünk megoldást biztosít bankok közötti átutalások elvégzésére. Természetesen csak logikájában fog hasonlóságot mutatni egy valós banki szoftverrel, hiszen egy pénzügyi rendszer az itt taglaltaknál jóval komplexebb. A tutorial során minden előálló kód fordítható és publikálható állapotban megtalálható a samples/tutorial könyvtárban, ahol a build-hez és publikáláshoz szükséges Maven2 script-ek is megtalálhatóak. Ezen fejezet célja, hogy egy példaprojekten keresztül mutassa be a Brillien-ben történő tervezés és fejlesztés elemi lépéseit, a presence-ek működését. !
Fontos, hogy a futtatáshoz szükségünk van egy xmpp szerverre, mely az üzenetek továbbításáért felelős.
Használható ejabberd, Openfire vagy ami a legjobban megfelel a majdani rendszerrel szemben támasztott követelményeknek. A Brillien konfigurálása a disztribúció data/config/services.xml állománya segítségével végezhető, így az xmpp szerverhez történő kapcsolódáshoz szükséges beállítások is. Alapértelmezett módon a prototype.brillien.org domain nevű xmpp szerverhez kíván csatlakozni a localhost 5222-es portján. Fontos követelmény, hogy a “liaison” felhasználónak nevével megegyező jelszóval és “új felhasználó létrehozás”-i joggal rendelkezésre kell állni az xmpp szerverben. Ezen könyvnek nem célja kitérni az xmpp szerverek használatára, azonban fontos kiemelni, hogy minden xmpp szervernek megvan a maga sajátossága konfigurációt illetően. Jelen könyv ejabberd-t használ XMPP szerver referenciaként. A konfiguráció ejabberd-nél a következő: - welcome message-et kikapcsolni: %%{welcome_message, {"Welcome!", "Welcome to this Jabber server."}},
F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
19
- regisztrációkra vonatkozó időkorlátot inaktiválni: {registration_timeout, infinity}. - biztosítani bármely presence számára a regisztráció lehetőségét: {access, register, [{allow, all}]}. - hálózati forgalomra vonatkozó korlátokat kijjebb tolni: {shaper, normal, {maxrate, 50000}}. {shaper, fast, {maxrate, 100000}}. A példában mind a ejabberd, mind példaszerver domain neve: prototype.brillien.org. Otthoni gépen a hosts (unix-os rendszereken /etc/hosts, windows-os rendszereken windows/system32/drivers/etc/hosts) állományba az alábbi új sor felvételével a megfelelő névfeloldás egyszerűen megvalósítható: 127.0.0.1 prototype.brillien.org
A beállított XMPP szerverünket elindítva, most már minden rendelkezésre áll, hogy használatba vegyük a Brillien példányunkat. A megfelelő startup script indításával ki is próbálhatjuk.
Tutorial 0: Hello !
Első, bevezető jellegű példánk, hogy írjunk egy presence-t mely minden bejövő kommunikációra egy “Hello”
üzenetet küld vissza. Ez a példa nem része banki alkalmazásunknak, csupán alapfogalmak bevezetésére szolgál. A Brillien saját Maven2 archetípust és plugin-t biztosít projektjeink kezelésére. Első lépésként telepítsük fel a source/maven könyvtárban található archetípust és plugin-t, a könyvtár két alkönyvtárában végrehajtott mvn install segítségével. Most két projektre lesz szükségünk: creditBlue a szerver oldal számára és creditClient a kliens oldal számára. A mvn archetype:create -DarchetypeGroupId=com.vii.brillien -DarchetypeArtifactId=brillien-archetype -DarchetypeVersion=10.01 -DgroupId=com.vii.brillien.tutorial -DartifactId=creditBlue mvn archetype:create -DarchetypeGroupId=com.vii.brillien -DarchetypeArtifactId=brillien-archetype -DarchetypeVersion=10.01 -DgroupId=com.vii.brillien.tutorial -DartifactId=creditClient
parancsok kiadásával az alábbi referenciastruktúra jön létre:
F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
20
(A két maven projekt használatba vételéhez szükséges a Brillien disztribúció helyének megadása is. A pom.xml
tag-jébe kell a disztribúció elérési útját írni. Az mvn package kiadásakor az elkészített Brillien komponens archív a megadott útvonal activator alkönyvtárába másolódik.) !
Szerver oldalon egyetlen osztály kerül definiálásra: com.acme.creditblue.DecentPresence. A Brillien-ben minden
entitás a com.vii.brillien.core.component.SuperPresence osztály leszármazottja, ebből az osztályból öröklődik a powerobject modell entitásokra vonatkozó implementációja. Az entitások publikációjának folyamata egyszerű. Minden betöltött osztály akkor tekintendő Brillien entitásnak, ha az osztálydefiníció rendelkezik @PresenceService annotációval vagy a SuperPresence osztály közvetlen leszármazottja. Minden más esetben egyszerű utility osztálynak minősül és nem kerül presence-ként publikálásra. Egy entitás osztálynak bármelyik SuperPresence osztály leszármazottja lehet ősosztálya, így saját típusokkal, funkciókkal lehet bővíteni a hierarchiát. Ez esetben a @PresenceService annotáció megléte szükséges. A @PresenceService annotáció feladata, hogy a presence működtetéséhez szükséges alapadatokat definiálja: publikálási név, naplózás szintje és típusa, periodikusság paraméterei, stb. Természetesen az annotáció hiánya esetén alapértelmezett értékek lesznek figyelembe véve, ahol minden presence az osztály nevén kerül publikálásra. A DecentPresence ősosztálya a SuperPresence, így automatikusan publikálásra kerül. !
Az entitások közötti kommunikációt a com.vii.brillien.kernel.axiom.component.Communication interfész definiálja,
melynek megfelelően minden kommunikációban megtalálható egy szöveges üzenet, opcionális paraméterei valamint a kapcsolódó adatok: időbélyeg, feladói és címzett adatok, stb. Egy üzenet érkezésekor a Brillien az üzenethez és paramétereihez
illeszthető
(név
és
megfeleltethető
paraméterlista)
metódust
keres,
vagyis
az
üzenetek
metódushívásokban öltenek formát a hívott entitásban. Az üzenetre küldendő válasz a metódus visszatérési értéke lesz, amennyiben ilyen létezik. Ha a metódus void vagy visszatérési értéke null, a Brillien feltételezi, hogy az adott entitás még kommunikáció(k)ra vár és a válasz később áll elő. Brillien-ben létezik egy általános üzenetfeldolgozó metódus is: public Object processMessage(String message, Object[] parameters) throws BrillienException;
Ez akkor hívódik meg, ha nem sikerült az üzenetnek megfelelő metódust találni. Jelen példánkban pont erre a funkcionalitásra van szükségünk: minden bejövő üzenetre “Hello” választ küldeni. Ehhez a DecentPresence osztály felül kell, hogy írja az ősosztályban definiált processMessage metódust: public class DecentPresence extends SuperPresence { @Override public Object processMessage(String message, Object[] parameters) throws BrillienException { return "Hello!"; } }
Ez a négy soros forráskód már egy igazi működő Brillien entitás. A build-et elvégezve a Brillien kicsomagolja a komponens archívot és log-jai között láthatjuk is a “Domo by decentpresence” üzenetet ami a presence-ünk publikálását jelenti. Finomíthatjuk jelen példánkat pár aprósággal. Vegyük fel a @PresenceService annotációt a DecentPresence osztálydefiníciónk elé, egyedi logolási szintet meghatározva. @PresenceService( logLevel = PresenceService.INFO )
Az Activator osztály feladata, hogy egy modul publikálása esetén szükséges műveleteket és validációkat elvégezze. Ehhez vegyük fel a BlueActivator osztályt a következő definícióval: public class BlueActivator { public static void main(String[] args) { System.out.println("My first Brillien module!!!"); F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
21
} }
Egy Activator használatához szükség lesz deployment descriptor használatára is, így a modulon belül megjelenik a config/activate.xml állomány a következő tartalommal: <deployment> <descriptionVersion>1.0 Acme LLC http://www.acme.com <description>Brillien Tutorial 0 <mainClass>com.acme.creditblue.BlueActivator
Az állomány definiálja a szükséges vendor információkat, valamint az Activator osztály minősített nevét mely alapján - a modul publikációjakor - a megfelelő osztály betöltésre kerül és main metódusa meghívásra kerül. Az mvn package parancs kiadásával a fordítás, csomagolás és publikálás végrehajtódik. A Brillien log-ban a következő bejegyzés figyelhető meg a komponens publikációját követően: “My first Brillien module!!!”
Az alkalmazás szerver oldala ezzel elkészült, most lássuk hogy néz ki a kliens oldal, a creditBlueClient modul. Működéséhez szükséges a Brillien clarity és ignition moduljai. A com.acme.creditblue.client.DecentClient osztály definíciója: public class DecentClient extends BrillienClient { public DecentClient(String address, int port, String xmppServerName, String username, String password) throws XMPPException { super(address, port, xmppServerName, username, password); } public String sendRequest() throws Exception { String msg = sendGet( 5000, "decentpresence", "sayHello" ).getResponse(); disconnect(); return msg; } public static void main(String[] args) throws Exception { DecentClient client = new DecentClient( "localhost", 5222, "prototype.brillien.org", "slinker", "slinker" ); String s = client.sendRequest(); System.out.println("Response: " + s); } }
A BrillienClient az ignition modul beépített osztálya, mely definiálja a legalapvetőbb műveleteket kliensek számára: - XMPP kapcsolat kezelése - Kommunikációk kezelése Célszerűen minden kliens ezen osztály alosztályaként kerül definiálásra. A main metódus példányt készít a kliens osztályból, melynek konstruktora az XMPP kapcsolat felépítéséhez szükséges adatokat várja. Az XMPP specifikációnak megfelelően a kliens és szerver által használt XMPP szerver gyakorta nem azonos. A példány létrejöttével a kliensünknek működő XMPP kapcsolata van. A sendRequest metódus működése F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
22
egyszerű: “sayHello” üzenetet küld a decentpresence entitásnak a sendAGet metódus által, majd a válasz megérkezéséig legfeljebb 5000 milliszekundumig várakozik. A megadott időkorlát túllépése kivétel kiváltódását eredményezi. !
Pár szóban a Brillien XMPP-alapú rétegéről: a Brillien minden presence-hez a neve kisbetűsítésével képzett
névvel rendelkező XMPP felhasználói fiókot hozza létre. Publikálás folyamán felépül a kapcsolat, online-ná válik a Brillien entitás, melynek minden példánya ezen a kapcsolaton keresztül kezeli kommunikációit. Maga a kommunikáció egyszerű szöveges üzenet, ahol az adatok JSON formátumban kerülnek továbbításra. Az üzenetek szöveges volta miatt mindegy, hogy a kommunikáció egy másik presence-től vagy chat programból vagy web oldalról jön. Több mód is rendelkezésünkre áll, amennyiben entitások közötti kommunikációt szeretnénk megvalósítani:
- sendGet : Szinkron üzenetküldés, ahol a feladó vagy az alapértelmezett vagy paraméterben specifikált időtartamig várakozik a válaszra, ami adott esetben egy hibaüzenet is lehet. Az időtartam túllépése kivétel kiváltódását eredményezi. - sendSet : Aszinkron üzenetküldés, ahol a küldő az üzenetküldést követően nem foglalkozik tovább a kommunikációval. Kézbesítését, így feldolgozását sem várja meg, választ sem vár. - sendAGet : Aszinkron üzenetküldés, ahol a válasz egy paraméterben megadott visszahívási pontra érkezik. - sendDelegatedGet : Aszinkron üzenetküldés, ahol a válasz egy paraméterben specifikált harmadik félnek kerül továbbításra. !
Jelen példánkban Java-alapú klienset használunk. A további kliens oldali fejlesztési lehetőségekről, köztük a
weboldalakba ágyazott kliensekről, későbbi tutorial-ok gondoskodnak. A futó publikált Brillien entitásunk mellett, ezen kliens fordítását és futtatását követően a kimeneten látni fogjuk a megkapott választ: Message:Connecting to: localhost 5222 prototype.brillien.org Message:Connecting with entity: slinker F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
23
Message:Sending Packet :: <message id="1BcPP-4" to="[email protected]">… sayHello… … Message response received::"Nice to talk with you!" Message:Connection closed tolocalhost:[email protected]/slinker
Amiből minket ez a sor érdekel különösen: “Nice to talk with you!”
Ezzel meg is vagyunk első komponensünkkel. Elég egyszerű volt, nem igaz?
Tutorial 1: A kliens !
Kezdjük el felépíteni a creditBlue alkalmazás belső logikáját! Először az átutalás adatstruktúrája kerül
definiálásra, amiben kliensünk fogja megadni az átutalás végrehajtásához szükséges adatokat. public class TransferRequest { private String senderName; private String senderAccountNumber; private String recipientName; private String recipientAccountNumber; private long amount; private String currency; }
A struktúra nagyon egyszerű, csak a legszükségesebb adatokat írja le. Az adattagokhoz set illetve get metódusokat kell generálni, a Bean modellnek megfelelően. Brillien-ben minden paraméterként megjelenő objektum JSON formátumban fog a kommunikációban továbbítódni, így a set/get metódusok megléte szükséges az objektumok átalakításánál. (megjegyzés: a JSON leképezés sajátossága, hogy tömbök nem alkalmazhatóak adattag típusként, használjunk listát helyette.) A kliens kódja is meglehetősen egyszerű, nagyon hasonlít az előző példában bemutatott kliensre: public class TransferClient extends BrillienClient { public TransferClient(String address, int port, String xmppServerName, String username, String password) throws XMPPException { super(address, port, xmppServerName, username, password); } public String sendRequest() throws Exception { TransferRequest request = new TransferRequest(); request.setAmount( 1000 ); request.setCurrency("Euro"); request.setRecipientAccountNumber("69287554532"); request.setSenderAccountNumber("692875732"); request.setSenderName("John Smith"); request.setRecipientName("Bill Smith"); String msg = sendGet( 5000, "transfer", "startTransaction", request ).getResponse(); disconnect(); return msg; } public static void main(String[] args) throws Exception { TransferClient client = new TransferClient( "localhost", 5222, "prototype.brillien.org", "slinker", "slinker" ); String s = client.sendRequest(); System.out.println("Response: " + s ); } F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
24
}
Felépíti a request nevű lokális átutalás objektumot és egyszerűen elküldi a transfer nevű Brillien entitásnak, majd megkapva az átutalás eredményét, kiíratja azt. Most írjuk meg a szerver oldali részét, mely fogadja az átutalást és visszaküld egy “Successful transfer.” üzenetet válaszként. A com.acme.creditblue csomagba új osztálydefiníciót helyezünk: public class Transfer extends SuperPresence { public String startTransaction( TransferRequest req ){ System.out.println(">>" + req); return "Successful transfer."; } }
!
A Transfer egy Brillien presence, mivel ősosztályaként a SuperPresence lett megjelölve. Egyetlen szolgáltató
metódusa van, a startTransaction, melyet a kliens egy TransferRequest paraméterrel hív meg. Szerver oldalon nincs semmi dolgunk ezzel kapcsolatban, nem kell annotációkkal ellátni, vagy egyéb módon jelölni, hogy a metódus szolgáltatást nyújt, hiszen minden szükséges transzformációt és validációt elvégez a Brillien a háttérben. A kliens hívását követően ezen osztály példányának startTransaction metódusa hívódik meg, és a visszatérési értéke kerül a kliens számára átküldésre. A creditBlue buildelését és publikációját követően, a kliens futtatásával láthatjuk a végrehajtás eredményét mind a szerver mind a kliens oldalt: >>TransferRequest{senderName='John High', senderAccountNumber='111111-222222-111111-111111', recipientName='Bill Smith', recipientAccountNumber='111111-111111-111111-111111', amount=1000, currency='Euro'} --- szerver oldali kiíratás Message response received::"Successful transfer." [] --- Kliens oldali kiíratás.
!
A Brillien minden presence-hez egy speciális objektumot társít: PresenceManager. Ezen objektum felelős az
entitás példányok életciklus-eseményei valamint kommunikációi kezeléséért. A sendGet hívásakor egy xmpp csomag került elküldésre, mely a címzetthez mint xmpp klienshez kerül, ami nem más, mint az adott entitáshoz rendelt PresenceManager objektum. Az xmpp hívás feldolgozásakor egy példány készül a Transfer entitásból, melynek - az xmpp csomagnak megfelelően - startTransaction metódusa hívódik meg, mely megvalósítja a szükséges kommunikációkat. Alapértelmezett esetben minden beérkező xmpp kommunikációhoz egy új entitás példány kerül társításra, azzal a kiegészítéssel hogy a példányok egy pool segítségével újrafelhasználásra kerülnek. Példánk ezzel elkészült.
Tutorial 2: A szerver oldal !
Bontsuk ki jobban a szerver oldali komponenst a következő struktúrának megfelelően:
F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
25
Három új fogalom jelent meg: - Cachier: Számlaműveletekért felelős entitás - CustomerDataProvider: Ügyféladatokkal kapcsolatos funkciókat biztosító entitás - Exchanger: Pénzintézetek közötti tranzakciókat kezelő entitás Ebben a példában felépítjük az entitások közötti kommunikációkat, az átutalás folyamatát. A Transfer entitás startTransaction metódusa ennek megfelelően változik és kommunikál a három új entitással: public String startTransaction( TransferRequest req ) throws BrillienException { System.out.println(">>" + req); try { if( sendGet( "customerdataprovider", "validateCustomerData", req.getSenderName(), req.getSenderAccountNumber() ).getResponse( ) ){ sendSet( "cachier", "deposit", req.getSenderName(), req.getSenderAccountNumber(), req.getAmount(), req.getCurrency() ); if( sendGet( "customerdataprovider", "validateCustomerData", req.getRecipientName(), req.getRecipientAccountNumber() ).getResponse( ) ){ sendSet( "cachier", "load", req.getRecipientName(), req.getRecipientAccountNumber(), -req.getAmount(), req.getCurrency() ); sendSet( "cachier", "load", req.getSenderName(), req.getSenderAccountNumber(), req.getAmount(), req.getCurrency() ); } else{ sendSet( "cachier", "load", req.getSenderName(), req.getSenderAccountNumber(), req.getAmount(), req.getCurrency() ); sendSet( "exchanger", "externalize", req.getSenderName(), req.getRecipientName(), req.getRecipientAccountNumber(), req.getAmount(), req.getCurrency() ); } } else return "Invalid client data!"; } catch (BrillienException e) { throw new BrillienException("Cannot handle request", e); } return "Successful transfer."; }
A példafejezet az entitások közötti kommunikációt szemlélteti, így nem tartalmaznak komplex üzleti logikát: @PresenceService public class Cachier extends SuperPresence { public String deposit( String owner, String accountNumber, long amount, String currency ){ // do something ... return "done"; } public String load( String owner, String accountNumber, long amount, String currency ){ // do something ... return "done"; } } @PresenceService public class CustomerDataProvider extends SuperPresence { public boolean validateCustomerData( String name, String accountNumber ){ return name.startsWith("John"); } } @PresenceService public class Exchanger extends SuperPresence { F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
26
public void externalize( String sender, String recipient, String accountNumber, long amount, String currency ){ // do something ... } }
A kód buildelése és futtatása után a következő log bejegyzéseket láthatjuk a Brillien kimenetén (karcsúsítva): >>TransferRequest{senderName='John High', senderAccountNumber='111111-222222-111111-111111', recipientName='Bill Smith', recipientAccountNumber='111111-111111-111111-111111', amount=1000, currency='Euro'} FINEST: Sending Packet :: <message id="dsKEw-4" to="[email protected]">…validateCustomerData… FINEST: Sending Packet :: <message id="dsKEw-4" to="[email protected]/rpc" from="[email protected]">…response:true… FINEST: Sending Packet :: <message id="dsKEw-4" to="[email protected]">…deposit… FINEST: Sending Packet :: <message id="dsKEw-4" to="[email protected]">…validateCustomerData… FINEST: Sending Packet :: <message id="dsKEw-4" to="[email protected]/rpc" from="[email protected]">…false… FINEST: Sending Packet :: <message id="dsKEw-4" to="[email protected]">…load… FINEST: Sending Packet :: <message id="dsKEw-4" to="[email protected]">… externalize… FINEST: Sending Packet :: <message id="dsKEw-4" to="[email protected]/rpc" from="[email protected]">…Successful transfer….
Ez szépen mutatja, hogy mely entitások, milyen xmpp csomagot küldözgettek egymásnak. Technikailag egy Java objektum írja le a kommunikációt mely JSON formátumúvá történő alakítást követően kerül a szöveges stanza-ba beágyazásra. !
A Brillien naplózási adataiból láthatóak, hogy hasonló naplózási szinttel kerülnek az alkalmazásszerver és az
alkalmazás kommunikációi naplózásra, mely keveredést célszerű későbbi feldolgozások végett elkerülni, így szerencsés az entitásainknál definiálni a naplózás szintjét. Ennek megfelelően a következő naplózási szintek kerülnek definiálásra: @PresenceService( logLevel = PresenceService.INFO ) public class Transfer extends SuperPresence { … } @PresenceService( logLevel = PresenceService.INFO ) public class Exchanger extends SuperPresence { … } @PresenceService( logLevel = PresenceService.FINER ) public class CustomerDataProvider extends SuperPresence { … } @PresenceService( logLevel = PresenceService.FINE ) public class Cachier extends SuperPresence { … }
Ezen beállítások eredményét a modul újbóli publikációját és a kliens újrafuttatását követően láthatjuk.
Tutorial 3: A Flow és a példányosítás menedzselése !
A teljes átutalás folyamat egy műveleti egység, oszthatatlan, modell szinten atomi. Az ügyfél kérését követően
indul a folyamat és a műveleteket leíró állapotautomata végállapotba kerülését követően leáll. Ez megfelel a powerobject modell Flow fogalmának. Ebből következik, hogy a Transfer egy Flow entitás lesz: @PresenceService( logLevel = PresenceService.INFO ) @Indispensable public class Transfer extends SuperFlow { @Activate public String startTransaction( TransferRequest req ) throws BrillienException { … } } F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
27
Az entitás immár a SuperFlow alosztálya, így példányai már folyamatként fognak a Brillien-ben megjelenni. Az @Indispensable annotáció feladata, hogy előírja a Brillien számára a folyamat reprodukálásának szükségességét, egy esetleges hiba esetél. Lehetnek olyan folyamatok, melyek esetleges sikertelen befejezése vagy teljes elvesztése (hardware hiba, stb.) nem okoz problémát, de bizonyos folyamatok kritikusak, így biztosítani kell, hogy bármilyen probléma esetén is végrehajtásra kerülnek. A Brillien két külső rendszert használ kommunikációinak kezelésére: xmpp szerver és couchdb, melyből az utóbbi a klaszterezési réteget és a Flow-k perzisztens tárolását jelenti. A couchdb használatához a Brillien services.xml konfigurációs állományában kell egy kapcsolót átállítani a következőknek megfelelően: on localhost
Így a Brillien induláskor kapcsolódik a couchdb adatbázishoz és a data/couchDesignDocuments könyvtár tartalmának megfelelően a design dokumentumai publikálásra kerülnek. Ez a feltétele annak, hogy az @Indispensable annotációval ellátott folyamatok végrehajtásának biztonsága megoldott legyen. (Megjegyzés: működés közben láthatjuk, hogy a couchdb a Brillien “heartbeat” eseményeit is tárolja, ami a klaszter node-jainak kezeléséhez szükséges. ) Minden flow példány speciális műveletekkel egészül ki, ezek közül a legfontosabbak: activate, terminate, suspend. Az activate felelős a folyamat elindításáért, melyhez esetlegesen szükséges adatokat a paraméterlistában kapja meg. A terminate az adott flow állapotterét egy Exit állapottal egészíti ki, ami jelzi, hogy a flow befejezte működését. A suspend feladata, hogy jelezze a flow időben való végrehajtásának szegmentációját, ami a flow későbbi folytatását jelzi a rendszer fele. Ilyenkor a Brillien gondoskodik a flow eltárolásáról, hogy egy később beérkező kommunikáció hatására ugyanott folytatódjon ahol abbamaradt. A perzisztencia alapértelmezett esetben az állapottér mentését jeleni, de könnyedén testre szabható, kiterjeszthető. Minden folyamat kezdődik valahol, valamilyen beérkező kommunikáció hatására elindul. Egy flow entitás minden olyan metódusa, mely a folyamatot, mint műveleti egységet hivatott aktiválni, az @Activate annotációval kell ellátni. Ebből entitásonként több is lehet. Az activate és terminate metódusokkal a fejlesztőnek nincs gondja. Amennyiben egy flow presence olyan kommunikációt kap, mely @Activate annotációval rendelkező metódushoz kerül továbbításra, a hívás delegálását megelőzően az activate metódus, azt követően pedig a terminate metódus kerül végrehajtásra. Ezen két művelet ellátja a flow kezeléshez szükséges legfőbb feladatokat. A terminate metódus csak abban az esetben kerül végrehajtásra, amennyiben az adott flow nem került felfüggesztésre a suspend metódus hívása által. !
Most vizsgáljuk meg a startTransaction metódust alaposabban. A sendSet hívások azt jelentik, hogy aszinkron
kommunikációt indít a transfer flow más entitások felé, de az üzenet jelentette művelet végrehajtását nem várja meg, így könnyen előfordulhat, hogy a transfer flow terminate metódusa már befejeződött, de a cachier entitás még nem dolgozta fel “load” üzenetét. Más szóval a flow egyik művelete akkor hajtódna végre, amikor a műveleti egység már elvben véget ért. Most az egyszerűbb utat választva, szinkron hívásokká alakítjuk a startTransaction kommunikációit, vagyis a sendSet hívások sendGet hívásokká cserélődnek. A powerobject modellnek köszönhetően minden kommunikáció aszinkron, így a sendGet működése is az, így a szinkronhívás megadott ideig tartó várakoztatással van megoldva. Az üzenet küldését követően egy megadott idő elteltéig várakozik az esetleges válaszra amivel visszatérhet vagy kivételt dob az időlimit túllépése esetén. Ezen időtartam mértéke konfigurálható a services.xml állományban található <packetReplyTimeout> tag segítségével. A finomított kód: @Activate public String startTransaction( TransferRequest req ) throws BrillienException { System.out.println(">>" + req); try { if( sendGet( "customerdataprovider", "validateCustomerData", req.getSenderName(), req.getSenderAccountNumber() ).getResponse( ) ){ sendGet( "cachier", "deposit", req.getSenderName(), req.getSenderAccountNumber(), req.getAmount(), req.getCurrency() );
F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
28
if( sendGet( "customerdataprovider", "validateCustomerData", req.getRecipientName(), req.getRecipientAccountNumber() ).getResponse( ) ){ sendGet( "cachier", "load", req.getRecipientName(), req.getRecipientAccountNumber(), -req.getAmount(), req.getCurrency() ); sendGet( "cachier", "load", req.getSenderName(), req.getSenderAccountNumber(), req.getAmount(), req.getCurrency() ); } else{ sendGet( "cachier", "load", req.getSenderName(), req.getSenderAccountNumber(), req.getAmount(), req.getCurrency() ); sendGet( "exchanger", "externalize", req.getSenderName(), req.getRecipientName(), req.getRecipientAccountNumber(), req.getAmount(), req.getCurrency() ); } } else return "Invalid client data!"; } catch (BrillienException e) { throw new BrillienException("Cannot handle request", e); } return "Successful transfer."; }
A kliensen nem szükséges módosításokat végezni. Futtassuk le! Kiemelt loggok a szerver oldalt: Message received: From: [email protected]/rpc To:[email protected] Message:startTransaction FINER: Request for get an instance for::SERVICES_FLOW of Transfer FINER: Creating sparkle instance: Transfer FINER: New presence added:::Transfer#Brillien#-922337203685477579212620131425861262013142586812000 to::Transfer INFO: Message-based communication received: startTransaction … // Sok-sok log a creditBlue belső kommunikációjáról FINEST: Sending Packet :: <message id="g7rrv-4" to="[email protected]/rpc" from="[email protected]">…Successful transfer… Output a kliens oldalt: Message response received::"Successful transfer." []
A kliens kommunikációja a transfer@xmppdomain címre érkezik, ahol egy Transfer típusú flow kerül példányosításra, mely megkapva a paraméterrel rendelkező startTransaction üzenetet, megkezdi működését, majd válaszát a kliensnek visszaküldi. Ez már egy tényleges Flow, a couchdb-ben megtaláljuk a dokumentumok között. Példányosításról: három példányosítási mód van a Brillien-ben definiálva: Sparkle, Consonant és Resident. - Sparkle: az alapértelmezett példányosítási mód, mely minden bejövő kommunikációra új presence példányt hoz létre. Ez apró funkciókat biztosító entitásoknál célszerű, melyek működése független az éppen alkalmazott üzleti logikától, egyfajta utility szolgáltatásokat nyújtva. De tipikusan ide sorolandók a flow típusú entitások is, hiszen bármikor bármennyi üzleti folyamat elindulhat egy valós alkalmazásban. Alapértelmezett esetben minden létrejövő @Sparkle presence példány a hívó folyamatához rendelődik. A @Sparkle annotáció dorky értékének igazra állításával lehetőség van ettől eltérni, és a példányokat a Brillien SERVICES_FLOW rendszerfolyamatához rendelni. Ezzel jelezhetjük, hogy ezen entitás nem része a hívó folyamatnak. - Consonant: flow szintű egyediséget biztosít. Egy flow-n belül csak egy példány készül az adott entitásból. A flow “műveleti egység” definícióját gondoljuk végig! Az egység azt jelenti, hogy minden belső kommunikációja, entitása ugyanazon cél, funkció része, melyek egy oszthatatlan egységben öltenek formát. Tehát entitásai csak ezen folyamat kommunikációban vesznek részt. A flow, mint művelet számos részeredmény, állapot vagy ha úgy tetszik párbeszéd megjelenését eredményezheti így ezen entitások a folyamat teljes életciklusa alatt léteznek nem szűnhetnek meg egyetlen kérés kiszolgálását követően. Amikor egy üzleti folyamatot modellezünk, entitásaink jó része ilyen típusú. Tekintve, hogy egy folyamat al-folyamatokat is tartalmazhat, és ez a tartalmazást mutató struktúra tetszőlegesen összetett lehet, felmerül a kérdés, hogy a fa struktúrát mutató flow hierarchia különböző szintjein vagy ágain lévő entitások közötti F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
29
kommunikáció hogyan hat ki a példányosítás folyamatára. Minden Consonant entitás példány alapértelmezetten ahhoz a folyamathoz rendelődik, ahonnan a kommunikáció érkezett. Egy entitás egy adott flow-típushoz társítása specifikálható a @Consonant annotáció paraméterezésével. A flowIDPrefix megmondja, hogy az adott entitás példányai kizárólagosan mely folyamatokhoz társíthatóak. - Resident: singleton pattern alkalmazása, azaz az adott entitásból csak egyetlen példány készül, alkalmazásszerver szinten egyedi. A Brillien szinte mindegyik rendszer-szintű entitása ilyen. Az adott példány a Brillien SERVICES_FLOW rendszerfolyamatához fog rendelődni. Minden @Resident entitás publikációjakor automatikusan példányosításra kerül. A három példányosítási mód könnyen elkülöníthető, egyszerűen megmondható egy entitásról, hogy milyen szintű egyediséget kíván meg működése. Üzleti logikától függetlenül, bármely más entitás számára nyújt valamilyen szolgáltatást? Műveletei párbeszéd-függetlenek? Bármilyen folyamatba beágyazható funkcionalitást valósít meg? Flow entitást definiálunk? Sparkle. Üzleti folyamataink belső entitásairól, vagy beágyazott folyamatairól kell döntenünk? Folyamat-szintű egyediség biztosítása szükséges? Consonant. Üzleti alkalmazásunk rendszerszintű szolgáltatásáról van szó? Bármely folyamat számára biztosított funkcióról van szó? Resident. Egyszerűen megválasztható annak fényében hogy az entitás milyen kommunikációt valósít meg és milyen entitásokkal fogja kommunikációit lebonyolítani. Most lássuk creditBlue alkalmazásunkat: @PresenceService( logLevel = PresenceService.INFO ) @Indispensable @Sparkle public class Transfer extends SuperFlow { … }
A Transfer egy flow típusú entitás, így Sparkle lesz a példányosítási módja. Ezt a @Sparkle annotáció elhelyezésével jelezhetjük. (Bár jelen esetben ez felesleges, hiszen ez az alapértelmezett mód.) A többi entitás definíciója a @Consonant annotációval kerül kiegészítésre tekintve, hogy a Transfer folyamat belső entitásai. @PresenceService( logLevel = PresenceService.INFO ) @Consonant public class Exchanger extends SuperPresence { … } @PresenceService( logLevel = PresenceService.FINER ) @Consonant public class CustomerDataProvider extends SuperPresence { … } @PresenceService( logLevel = PresenceService.FINE ) @Consonant public class Cachier extends SuperPresence { … }
A klienst lefuttatva, banki szolgáltatásunk még mindig működik. Ha az olvasónak ideje és türelme engedi, akkor kipróbálhatja, hogyha az entitások több üzenetet küldenek egymásnak akkor az annotációjuknak megfelelően lesznek példányosítva. Minden beérkező kliens kommunikációra új Transfer folyamat indul, ám a folyamaton belül entitásokból mindig csak egy-egy készül a folyamat életciklusa alatt. !
Kiegészítés: Elég sokat hallani a “stateless design”-ról, így hasznosnak tűnik az állapotok kezeléséről
elgondolkodni. Természetesen nem teljes állapotmentességről van szó, hanem párbeszéd-függetlenségről ahol minden kérés független tranzakcióként kezelendő. Így megjelenik egy határ, ahol az állapotokkal rendelkező csomópontokat stateless módon kapcsolom össze. Állapotok nélkül egy összeadást sem lehetne elvégezni, nemhogy adatbázis tranzakciót, így az egyensúly megtalálása fontos modellezési kérdés. Fontos látni, érezni, tudni, hogy hol kezdődik az állapotmentesség mint tervezési követelmény és hol ér véget az állapotok nyilvántartásának szükségessége.
F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
30
Tutorial 4: Időbeli távolságok kezelése !
A Transfer entitás startTransaction metódusa meglehetősen egyszerű logikát követ, hiszen minden
kommunikációja szinkron. Jelen példánkban, a bankok közötti tranzakciónál, felmerülhet az időbeli távolságok problémája, vagyis amikor egy külső forrás kommunikációja nem kezelhető szinkron hívásokkal, és várakozást sem célszerű alkalmazni, a kommunikáció érkezésének bizonytalansági tényezője folytán. A powerobject modell szabad kommunikációjának követelménye miatt a modell bármely implementációja képes kezelni a kommunikációk során bekövetkező időbeli távolságokat. Ez nem is olyan egyszerű feladat egy elosztott és esetlegesen klaszterezett környezetben, ahol az objektumok életciklusa rendkívül dinamikus és az entitások számára semmiképpen sem determinisztikus. A powerobject az alkalmazás fogalma, mely “flow-k halmaza”-ként lett definiálva mutatja, hogy a vágyott üzleti problémánkat folyamatokként szemléljük. A Flow az időbeli távolságok kezelésének alappillére. Lássuk hogyan. Első lépésként vezessünk be (maven-nel generáltassunk le) egy új modult és egy új entitást, egy másik bankot, mely a bankok közötti átutalásnál végpontként szolgál. mvn archetype:create -DarchetypeGroupId=com.vii.brillien -DarchetypeArtifactId=brillien-archetype -DarchetypeVersion=10.01 -DgroupId=com.vii.brillien.tutorial -DartifactId=monopolite
Struktúránk az alábbiaknak megfelelően finomodik:
A szükséges osztálydefiníció: package com.elith.monopolite; @PresenceService( logLevel = PresenceService.INFO ) @Sparkle( dorky = true ) public class Arranger extends SuperPresence { public String arrange( String sender, String recipient, String accountNumber, long amount, String currency ){ try { Thread.sleep( 3000 ); } catch (InterruptedException e) { e.printStackTrace(); } return "Successful"; } }
Tekintve, hogy az Arranger a Monopolite bank entitása, így nem lehet része a Transfer folyamatnak, így az entitás példányosítási módja @Sparkle lett, a dorky flag igaz értékű paraméterezésével. A dorky flag biztosítja, hogy az entitás példányai a Brillien rendszerfolyamatához rendelődik. Az osztály egyszerű stub-ot reprezentál, fix késleltetéssel és konstans visszatérési értékkel. F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
31
Végezzük el fordítást és publikálást. Egyszerű entitás van benne, nem több egy egyszerű stub-nál. Tekintve, hogy a bankok közötti átutalásért az Exchanger entitás felel, definícióját finomítanunk kell, hogy aszinkron üzenetet küldjön új stub-unknak: public class Exchanger extends SuperPresence { public void externalize( String sender, String recipient, String accountNumber, long amount, String currency ) throws BrillienException { sendAGet( "arranger", "arrange", "done", sender, recipient, accountNumber, amount, currency ); flow.suspend(); } public void done( String message ){ System.out.println("Transaction done."); } }
Az Exchanger arrange üzenetet küld a távoli banknak, jelezve, hogy a választ a ”done” visszahívási pontra kell küldeni. A flow.suspend(); a szál felfüggesztését végzi, ami azt jelenti, hogy a hívás hatására a Brillien az adott folyamat állapotterét perzisztálja, erőforrásait felszabadítja. Az Arranger entitás válaszának megérkezésekor a folyamat felélesztésre kerül és az Exchanger entitás megkapja az üzenetet. A couchdb biztosítja számunkra a perzisztencia réteget. Jelen példánkban azt a problémát kezeljük, amikor bizonytalan, hogy egy folyamat részeként megjelenő kommunikáció mikor fejeződik be. Az Exchanger-ben elvégzett változtatások okán a Transfer flow sem használhatja tovább a szinkron hívást, hiszen bár az Exchanger feldolgozta üzenetét, mégsem zárulhat le a folyamat mindaddig, míg az Arranger entitás el nem küldi válaszát. A probléma megoldásának ismertetése előtt vizsgáljuk meg kicsit a kommunikáció folyamatát! A Brillien-ben minden hívás aszinkron. Tekintve, hogy egy presence akárhány kommunikációt képes indítani és fogadni, felmerűl a kérdés: hogyan tartja a Brillien harmóniában a kommunikációkat? Tegyük fel, hogy egy “A” entitás kommunikációba lép “B”-vel, mely presence a válasz megalkotásához kommunikált “C” és “D” entitással. Tehát “B” üzenetfeldolgozó metódusa hiába fejeződik be, választ nem küldhet, hiszen meg kell várnia “C” és/vagy “D” válaszát. “B” csak kommunikációi lezárását követően tudja válaszát előállítani és elküldeni “A”-nak. A válasz küldésekor a rendszernek tudnia kell, hogy mely kommunikációhoz tartozik a válasz. Mindeközben A, B, C, D entitások tetszőlegesen sok üzenetet kaphatnak, akár egymással is több párbeszédet tarthatnak fent egyszerre. Technikai szinten minden üzenetnek van egy egyedi azonosítója. Egy kommunikációra érkező válasz azonosítója természetes módon megegyezik a forrás üzenetével, a párosíthatóság érdekében. Amennyiben egy entitás kommunikációt kap, minden indított kommunikációja az üzenet feldolgozása során, örökli ezen azonosítót. Ez rekurzív definíció, hiszen az indított kommunikációk is indukálhatnak további kommunikációkat. Ez lehetőséget ad a Brillien számára, hogy bármely, egy presence-hez beérkező válasz típusú üzenet esetén, párosítani tudja az eredeti forrással. A párosításokhoz szükséges perzisztenciát itt is a couchdb biztosítja. Most lássuk, hogyan is működnek a különböző “send” hívások: - sendGet : Szinkron üzenetküldés, ahol a feladó vagy az alapértelmezett vagy paraméterben specifikált időtartamig várakozik a válaszra, ami adott esetben egy hibaüzenet is lehet. Az adott időtartam leteltéig vagy a válasz megérkezéséig a metódus végrehajtása blokkolódik. A címzett entitás az üzenet feldolgozását jelentő metódusa visszatérési értéke kerül válaszként elküldésre, ha létezik. Null visszatérési érték vagy void metódus esetén, a válasz még nem áll rendelkezésre, az adott entitásnak van legalább egy függőben lévő kommunikációja. Ennek vagy ezek lezárásával, előállhat a válasz, mely azonnal visszaküldésre kerül. - sendAGet : Aszinkron üzenetküldés, ahol a válasz egy paraméterben megadott visszahívási pontra érkezik. A feladó az üzenet küldésekor megjelöl egy visszahívási pontot, melyre az választ várja. Nincs időkorlát a válasz megérkezésére, nem blokkolódik a végrehajtás a válasz megérkezéséig. Ez fontos tervezési szempont, hiszen időkorlát híján a feladó a végtelenségig várhat egy üzenetre, mely lehet, hogy sosem érkezik meg.
F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
32
- sendSet : Aszinkron üzenetküldés, ahol a küldő az üzenetküldést követően nem foglalkozik tovább a kommunikációval, kézbesítését, így feldolgozását sem várja meg, választ sem vár. Ez azt jelenti, hogy a beérkező kommunikáció feldolgozásakor, az üzenetnek megfeleltetett metódus hiába rendelkezik visszatérési értékkel, a rendszer figyelmen kívül hagyja. (Megjegyzés: Ez nem korlátozza a címzettet, hogy később viszontüzenetet küldjön a feladónak, de ez modellezésileg azt jelenti, hogy a feladó minden ki- és beérkező kommunikációja függetlenek.) - sendDelegatedGet :
Ez valójában egy egyszerű kommunikáció kompozíció. A feladó választ igénylő, aszinkron
kommunikációt küld a címzettnek, de az előálló választ egy harmadik félnek kéri továbbítani. Get-típusú hívás abban az értelemben, hogy a címzett a hívást követően választ állít elő és el is küldi azt. Viszont set-típusú is, hiszen mind a feladó, mind a címzett csak a saját üzenete feladásáig foglalkozik az adott kommunikációval, kézbesítésével, feldolgozásával nem. !
Most lássuk, hogyan is kell felépíteni egy üzleti alkalmazás kommunikációs hálóját. Eddigi entitás példákban
csak sendGet és sendSet metódusok voltak használva, most lássuk hogyan változik egy alkalmazás képe a sendAGet megjelenésével. A Transfer folyamatunk Exchanger entitással folytatott kommunikációja aszinkronnál válik, hiszen nem várható el, hogy az átutalás azonnal befejeződjön: @PresenceService( logLevel = PresenceService.INFO ) @Indispensable @Sparkle public class Transfer extends SuperFlow { @Activate public String startTransaction( TransferRequest req ) throws BrillienException { System.out.println(">>" + req); try { if( sendGet( "customerdataprovider", "validateCustomerData", req.getSenderName(), req.getSenderAccountNumber() ).getResponse( ) ){ sendGet( "cachier", "deposit", req.getSenderName(), req.getSenderAccountNumber(), req.getAmount(), req.getCurrency() ); if( sendGet( "customerdataprovider", "validateCustomerData", req.getRecipientName(), req.getRecipientAccountNumber() ).getResponse( ) ){ sendGet( "cachier", "load", req.getRecipientName(), req.getRecipientAccountNumber(), -req.getAmount(), req.getCurrency() ); sendGet( "cachier", "load", req.getSenderName(), req.getSenderAccountNumber(), req.getAmount(), req.getCurrency() ); return "Successful transfer."; } else{ sendGet( "cachier", "load", req.getSenderName(), req.getSenderAccountNumber(), req.getAmount(), req.getCurrency() ); sendAGet( "exchanger", "externalize", "externalized", req.getSenderName(), req.getRecipientName(), req.getRecipientAccountNumber(), req.getAmount(), req.getCurrency() ); } } else return "Invalid client data!"; } catch (BrillienException e) { throw new BrillienException("Cannot handle request", e); } return null; } public String externalised( String message ){ System.out.println("Externalised:" + message); return message + " transfer"; } }
A kliens sendGet metódusa,a paraméterként megadott ideig, aszinkron módon várja a Transfer folyamat befejeződésének eredményét válaszként. A kliens elküldi “startTransaction” üzenetét, ami nyilván egy metódushívást fog eredményezni a F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
33
Transfer entitásban. Ezen metódus @Activate annotációjának okán a kommunikáció folyamat aktiválásnak is minősül, így a Brillien elindítja a Transfer-t mint folyamatot. (Megjegyzés: sose felejtsük el, hogy egy entitás hiába flow, kommunikációja szabad, így lehetnek az adott presence flow tulajdonságától független kommunikációi is.) A startTransaction belső átutalás esetén a “"Successful transfer." válasszal tér vissza, amit a Brillien válaszként el is küld a kliensek. Amennyiben külső átutalásról van szó, null visszatérési érték jelzi a Brillien számára, hogy a kliens hívására küldendő válasz még nem áll rendelkezésre. A Transfer folyamat akkor zárul le, ha belső átutalás történt és a startTransaction metódus befejezte működését, vagy külső átutalás esetén az Exchanger entitás done metódusa “externalized” üzenetet küld a folyamatnak. A done metódus a következő képpen finomodik: public String done( String message ){ System.out.println("Transaction done."); return message; }
A kliens külső átutalást kezdeményezve elindítja a Transfer folyamatot, mely aszinkron üzenetet küld az Exchanger entitásnak. Ez egyfajta interfészként bonyolítja le az átutalást a Monopolite bankkal, mely az átutalást követően értesíti a sikerességről az Exchanger entitást, mely a megkapott választ továbbítja a Transfer entitásnak egy “externalized” üzenetben.
Ez
a
klienstől
beérkező
még
megválaszolatlan
kérés
feldolgozása
során
indított
aszinkron
kommunikációjának a lezárása, így az externalized metódus visszatérési értéke a creditBlueClient kliensnek küldött válasz is egyben. Egyszerű kommunikáció, semmi több. A kódon látszik a legjobban, hogy mennyire csak kommunikáció az egész, és formájában is végtelenül egyszerű. !
Vizsgáljuk meg, mi is történik egy folyamat felfüggesztésekor és felélesztésekor! Az Exchanger entitás
flow.suspend(); metódusa azon folyamatot (Transfer), melyben ő részt vesz, felfüggeszti. Felfüggesztéskor perzisztálásra kerül a folyamat egyedi azonosítója és állapottere. Az állapottér nemcsak a folyamat mint entitás állapotterét tartalmazhatja, lehetőség van kiegészíteni olyan információkkal, melyek a folyamat újraindításához, működéséhez szükségesek. Az adatok a couchdb-be kerülnek tárolásra, mely vagy az adott folyamathoz beérkező kommunikáció hatására vagy az adott, klaszterben használt Brillien node-ban bekövetkezett probléma kezelésékor kerül felolvasásra. A monopolite válaszának beérkezésekor a Brillien-nek újra fel kell építenie a folyamatot a couchdb-ből kinyert adatok alapján. Így a kommunikáció érkezésekor a Transfer folyamat és a Exchanger entitás felélednek, majd végbemegy a kommunikáció feldolgozása és a folyamat lezárása. Pár gondolat az erőforrások felszabadításáról. A Brillien egyik legalapvetőbb entitása a ReferenceRetriever, melynek feladata, hogy periodikusan (a services.xml-ben található által definiált gyakoriságnak megfelelően) minden futó flow-t megvizsgál, hogy végez-e bármilyen kommunikációt vagy sem. Amennyiben a flow már inaktív, megszünteti azt és minden elemét. Ez gyakorlatilag a flow, mint halmaz elemeire elindított rekurzív megszüntetést jelenti. Egy presence kommunikációban van, amennyiben: - az állapottere aktív és nem tartalmaz “Exit” státuszú állapotot. - isWaitingForResponse metódusa igaz értékkel tér vissza. Egy Unit entitás kommunikációban van, amennyiben: - mint Presence kommunikációban van - bármely eleme kommunikációban van Egy Flow entitás kommunikációban van, amennyiben: - mint Unit kommunikációban van F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
34
- felfüggesztésre került a suspend metódus által Ennek fényében felismerhető, hogy könnyen előfordulhat az az eset, amikor a Transfer entitás startTransaction metódusa befejeződött, de az Exchanger még nem kezdte meg kommunikációját a külső bankkal. Vagyis a flow abban az időpillanatban nem kommunikál, így erőforrásai felszabadításra kerülnek. Ezt elkerülendő, minden presence által indított sendAGet típusú kommunikáció esetén az entitás isWaitingForResponse metódusa igaz értékkel kell, hogy visszatérjen a válasz megérkezésig. Valahogy így: @Activate public String startTransaction( TransferRequest req ) throws BrillienException { System.out.println(">>" + req); try { if( sendGet( "customerdataprovider", "validateCustomerData", req.getSenderName(), req.getSenderAccountNumber() ).getResponse( ) ){ sendGet( "cachier", "deposit", req.getSenderName(), req.getSenderAccountNumber(), req.getAmount(), req.getCurrency() ); if( sendGet( "customerdataprovider", "validateCustomerData", req.getRecipientName(), req.getRecipientAccountNumber() ).getResponse( ) ){ sendGet( "cachier", "load", req.getRecipientName(), req.getRecipientAccountNumber(), -req.getAmount(), req.getCurrency() ); sendGet( "cachier", "load", req.getSenderName(), req.getSenderAccountNumber(), req.getAmount(), req.getCurrency() ); return "Successful transfer."; } else{ sendGet( "cachier", "load", req.getSenderName(), req.getSenderAccountNumber(), req.getAmount(), req.getCurrency() ); sendAGet( "exchanger", "externalize", "externalized", req.getSenderName(), req.getRecipientName(), req.getRecipientAccountNumber(), req.getAmount(), req.getCurrency() ); waitingForResponse = true; } } } catch (BrillienException e) { throw new BrillienException("Cannot handle request", e); } return null; } public String externalized( String message ){ System.out.println("Externalized:" + message); waitingForResponse = false; return message + " transfer"; }
Látszik, hogy az Exchanger entitásnak küldött üzenet küldésekor a folyamat jelzi, hogy válaszra vár, azaz aktív kommunikációban van, nem felszabadítható erőforrás. Ezen “állapotát” az Exchanger-től kapott válasza változtatja meg a waitingForResponse = false; utasítással. Elég favágó munkának tűnik mindig állítgatni ezt, mihelyt egy entitás aszinkron válaszköteles kommunikációt kezdeményez. A Brillien-ben minden entitás, mely sendAGet metódussal kommunikációt kezdeményez, vagy annak eredményét fogadja jelentést küld az őt tartalmazó flow entitásnak az eseményről. Az adott folyamat képes megmondani, hogy a folyamatban szereplő entitások várakoznak-e valamilyen válasz érkezésére. Ez egy aprócska automatizmus, mely által nem kell “kézzel” gondoskodni a waitingForResponse értékének állításáról. Így a fenti kódot nem kell átvezetnünk forráskódunkba. Fontos megjegyezni, hogy ez nem terjed ki a sendDelegatedGet kommunikációk figyelésére! Problémánkat ezzel megoldottuk. Buildelést és futtatást követően a következő eredményt látható: Message response received::"Successful transfer"
F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
35
!
Kiegészítés: Csak a folyamatok számára van biztosítva a perzisztencia, így egy @Resident példányosítási
eljárással rendelkező entitás állapotai tárolását a felhasználási helyét jelentő folyamat állapotterének kiterjesztésével lehet megoldani. Az adott folyamat expandStateSpace és recollectStateSpace metódusai polimorfizmusával saját tartalommal egészíthetjük ki a tárolandó adatokat: @Override protected void expandStateSpace(HashMap space) { space.put( "time", System.currentTimeMillis() ); } @Override protected void recollectStateSpace( HashMap space ){ System.out.println("Time::" + space.get("time") ); }
A tárolandó adatokat egy HashMap<String,Object> kollekcióba kell elhelyezni, melyről az expandStateSpace metódus gondoskodik. Felélesztéskor a kollekcióból a recollectStateSpace gyűjti ki a kívánt tartalmaz. A fent ismertetett kód csak egy aprócska példa, csak a funkciót demonstrálja.
Tutorial 5: Ügyfél adatbázis bevezetése A legalapvetőbb kommunikációkat és működéseket megismerését követően, a folyamatunkban található stub-ok működését kibonthatjuk. A mostani tutorial a CustomerDataProvider és a Cachier entitásokon keresztül mutatja be az adatbázisok kezelésének legfőbb módjait, az entitások paraméterezhetőségének lehetőségeit. A Brillien mind a javax.sql.DataSource, mind a javax.sql.XADataSource használatát lehetővé teszi, a felhasználó igényeinek megfelelően. Ennek megismeréséhez, első lépésben ismerjük meg a Brillien egyik speciális presence típusát a com.vii.brillien.core.component.db.DataSource-t, mely a következő adattagokat definiálja: String String String String String String boolean
vendor; userName; password; address; port; database; xaCapable;
Az entitás ezen adattagok értékeinek megfelelően kapcsolódik egy adatbázishoz, melyre pool-ba szervezett dataSource-t épít fel. A vendor információ a Brillien számára különösen fontos, hiszen a kapcsolódási URL-ek, JDBC driverek nevei vendor specifikusak. Három adatbázis-kezelő rendszer van beépített módon támogatva a Brillien-ben: PostgreSQL, MySQL és Oracle. A támogatott rendszerek köre könnyen kiterjeszthető, erről majd a későbbiekben. Azt, hogy a Brillien által felépített kapcsolat és DataSource XA-képes legyen-e a xaCapable adattag szabályozza, mely alapértelmezett esetben hamis értékkel rendelkezik. A többi adattag a kapcsolat létrehozásához szükséges adatokat definiálja. A példához egy PostgreSQL 8.4 fogja az adatokat szolgáltatni. Minden szükséges sql utasítás megtalálható a tutorial sql alkönyvtárában. Első lépésként egy credit nevű és jelszavú felhasználót hozunk létre (create_role.sql), majd egy creditblue nevű adatbázist (create_db.sql) tábláink számára. Táblastruktúráink (create_table_client.sql és create_table_account.sql): CREATE TABLE client ( id integer NOT NULL, "name" text NOT NULL, CONSTRAINT client_pk PRIMARY KEY (id) ) WITH ( OIDS=FALSE ); F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
36
CREATE TABLE account ( id text NOT NULL, owner_id integer NOT NULL, balance numeric NOT NULL, currency text NOT NULL, CONSTRAINT account_pk PRIMARY KEY (id), CONSTRAINT account_fk FOREIGN KEY (owner_id) REFERENCES client (id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE NO ACTION ) WITH ( OIDS=FALSE );
Most tegyünk bele pár példaadatot (insert.sql): INSERT INTO client (id, name) VALUES (1453633284, 'John Smith'); INSERT INTO account (id, owner_id, balance, currency) VALUES (692875732, 1453633284, 20000, 'Euro');
A tutorial minél egyszerűbb felépítettsége céljából több adatra most nincs szükségünk. A DataSource entitásunkat célszerűen külön modulba szervezzük, biztosítva, hogy az üzleti alkalmazás moduljaitól függetlenül lehet publikálni, frissíteni vagy adott esetben visszavonni. Rendszerünk így néz ki:
Új creditData modulunk a következő definíciót tartalmazza: @PresenceService @Resident public class CustomerDataConnector extends DataSource { }
Egy DataSource feladata, hogy üzleti alkalmazásaink entitásai számára adatbázis kapcsolatot biztosítson, így - ahogy jelent
esetünkben
is
-
a
legtöbb
esetben
@Resident
példányosítási
móddal
rendelkezik.
A
com.vii.brillien.core.component.db.DataSource osztály által definiált adattagok értékadását többféle módon is elvégezhetjük. A legegyszerűbb eset az inicializáló blokk használata: @PresenceService @Resident public class CustomerDataConnector extends DataSource { { F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
37
vendor userName password address port database xaCapable
= = = = = = =
"PostgreSQL"; "credit"; "credit"; "localhost"; "5432"; "creditblue"; false;
} }
A fenti példa sok felesleges elemet tartalmaz. A xaCapable alapértelmezett értéke is false, így ennek állítása szükségtelen. A Brillien a vendor információ és a xaCapable értéke alapján választja ki és tölti be a megfelelő meghajtót, valamint választja ki a megfelelő kapcsolódási URL-t. Az address alapértelmezett értéke a “localhost”, míg a porthoz az adott vendor által használt default portot rendeli. Ennek függvényében a következő inicializáló blokk is helyes: @PresenceService @Resident public class CustomerDataConnector extends DataSource { { vendor = "PostgreSQL"; userName = "credit"; password = "credit"; database = "creditblue"; } }
A DataSource-ok adatainak inicializáció blokkal történő megadása nem túl elegáns, hiszen egy már elkészült és lefordított komponensnél nem egyszerű megállapítani milyen beállításokkal rendelkezik, az esetlegesen bekövetkező változtatások pedig mindig fordítási munkát eredményeznek. A Brillien megengedi, hogy entitásokat a konfigurációs állományon keresztül lehessen adatokkal feltölteni, vagyis az activate.xml állományba elhelyezett értékeket a Brillien az adott entitás példányosításakor annak adattagjaihoz rendeli vagy közvetlen értékadás vagy az adattaghoz rendelt set metódus által. Három típusú adattaghoz lehet értéket rendelni: - String vagy primitív - List<String> - HashMap<String,String>. Az entitások paraméterezése a következő struktúrával írható le: <presence> qualifiedName <parameters> <parameter> fieldName fieldValue … <parameterList> fieldName2 value1 valueM … <parameterMap> fieldName3 F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
38
- key1 value1
… - keyX valueX
Egy presence definíciójának vagy kezelésének kiterjesztésére szolgál a <presence> tag, melyet az adott modul activate.xml állományában kell felvenni.
Ezen tag-ben kell megadni a presence minősített nevét és jelen esetben
adattagjainak értékét. Egy adattaghoz rendelt paramétereket a <parameter> tag tartalmazza, melyet a <parameters> tagban kell felvenni. Az adattag típusától függően ezen <parameter> tag tartalmazhat <parameterMap>, <parameterList> vagy <parameter> tag-eket. A mi esetünkben a következőképpen alakul az activate.xml tartalma: <presence> com.acme.creditblue.db.CustomerDataConnector <parameters> <parameter> vendor PostgreSQL <parameter> userName credit <parameter> password credit <parameter> database creditblue
Ennek fényében az osztálydefiníción is el kell végezni némi finomítást: @PresenceService @Resident public class CustomerDataConnector extends DataSource { }
Az entitás működéséhez szükséges JDBC driver-t (postgresql-8.4-701.jdbc4.jar) fel kell vennünk a pom.xml függőségeket tartalmazó tag-jébe az alábbiak szerint: <dependencies> <dependency> com.vii.brillien <artifactId>core <scope>provided [10.05-SNAPSHOT,) F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
39
<dependency> postgresql <artifactId>postgresql <scope>compile 8.4-701.jdbc4
(Megjegyzés: minden hivatkozott, nem provided típusú library bemásolódik a Brillien komponens archív lib könyvtárába.) Most már fordíthatjuk, publikálhatjuk creditData komponensünket. Futtatáskor a következő log bejegyzésre lehetünk figyelmesek: CONFIG: Successfull DataSource connection test for : CustomerDataConnector
!
A Brillien minden DataSource-t tesztel publikálása során. Az adott entitásból egy példányt készít, melytől
Connection objektumot próbál szerezni. Bármilyen probléma esetén az adott komponens publikációja leáll. A publikációt követően, a DataSource példányosítási módjának megfelelően, a presence példányok adatbázis kapcsolatot kérhetnek az adott DataSource-tól, a getConnection( DataSourceName ) híváson keresztül. A megkapott java.sql.Connection objektumon keresztül elvégezhetőek a szükséges műveletek. Adatbázis-kezelésnél különösen fontossá válik, hogy értsük modell szinten mit jelentenek a példányosítási módok.
!
A @Resident azt jelenti, hogy az entitásból egyetlen példány készül, mely az alkalmazásszerver életciklusa
végéig létezik. Ezalatt bármely entitástól kaphat kommunikációt, nem tekinthető egy flow részének sem, háttérfolyamatok és rendszerszintű szolgáltatások eszköze. Amennyiben az adott entitás egy-egy kommunikációja adatbázis-műveletet igényel, akkor adatbázis-kapcsolati szinten nem lehet kapcsolat kommunikációi között, vagyis az F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
40
adatbázis kapcsolat és ahhoz kapcsolódó objektumok megosztása tiltott. Egy alkalmazásszerver életciklusa alatt akárhány entitás kezdeményezhet kommunikációt, mely minden esetben aszinkron, így a kommunikációk végrehajtása nem determinisztikus. Röviden: minden adott kommunikációt érintő adatbázis-művelet a kommunikációt kezelő metóduson belül hajtódik végre. Itt jön létre a kapcsolat és ott is zárul le. (Megjegyzés: ezt nem kényszeríti ki semmi sem a Brillien-ben, sértené a kommunikáció szabadságát, hiszen létezhet olyan eset, ahol szükség lehet másként felépíteni egy adott entitást. Ugyanakkor a megszorítások hiánya nagyobb gondosságot igényel a tervezés oldaláról. ) A @Sparkle, a minden beérkező kommunikáció hatására új példányként megjelenő entitás, vagyis egyetlen példány sem fog egynél több kommunikációt feldolgozni.. Így követelménynek tekinthető a kommunikációk függetlensége, ebből kifolyólag a @Resident-tel megegyező adatbázis-kezelési elv van érvényben. A @Consonant a különösen érdekes eset, hiszen minden @Consonant entitás egy flow, más szóval műveleti egység, részeként jelenik meg. A műveleti egység oszthatatlansága érvényben van továbbra is, így a flow alatt végrehajtott adatbázis műveletek vagy sikeresen végrehajtódnak vagy visszavonásra kerülnek. Ilyen értelemben a Flow egy adatbázis tranzakcióként jelenik meg, ahol a Brilien-nek kell gondoskodnia a tranzakciókezeléshez szükséges eszközökről és automatizmusokról. Ez nem zárja ki azt, hogy egy @Consonant entitás @Resident vagy @Sparkle entitásokkal kommunikáljon, de ekkor a @Resident illetve @Sparkle entitások adatbázis-műveletei nem vehetnek részt az adott folyamat jelentette tranzakcióban. Amikor egy @Consonant presence adatbázis kapcsolatot kér le a getConnection metóduson keresztül, akkor a tartalmazó folyamat adatbázis tranzakcióként kezd működni, és minden XA-képes és nem XA-képes kapcsolatot nyilvántart és kezel, melyet @Consonant entitásai kértek le. A folyamat befejeződésével, commit vagy rollback fog végrehajtódni annak függvényében hogy sikeres volt-e a folyamat végrehajtása. Jelen példánkban a Cachier és CustomerDataProvider nevű @Consonant entitások fogják a korábban publikált CustomerDataConnector által nyújtott Connection objektumokat használni. Lássuk hogyan: public class CustomerDataProvider extends SuperPresence { private Connection c; @Override public void plan() throws BrillienException { c = getConnection("CustomerDataConnector"); } public boolean validateCustomerData( String name, String accountNumber ) throws BrillienException { PreparedStatement pst = null; ResultSet r = null; try { pst = c.prepareStatement( "SELECT c.id FROM client AS c WHERE c.\"name\"= ? AND c.id IN (SELECT a.owner_id FROM account AS a WHERE a.id=?);" ); pst.setString( 1, name ); pst.setString( 2, accountNumber ); r = pst.executeQuery(); boolean b = r.next(); return b; } catch (SQLException e) { throw new BrillienException( e ); } finally { try { r.close(); pst.close(); } catch (Exception e) { e.printStackTrace(); } } } }
F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
41
!
Brillien minden entitás példányosításakor, egyebek mellett, adattagokhoz rendeli a konfigurációs állományból
beolvasott paramétereket és meghívja a plan metódust.. A plan meghívása “jelzés” a presence példány számára, hogy most került példányosításra, és lehetősége van valamilyen inicializációs műveletet elvégezni. Példánkban a CustomerDataProvider presence adatbázis kapcsolatot kér, mely kérés, a presence @Consonant példányosítási módját figyelembe véve, a tartalmazó folyamathoz kerül delegálásra. Egy flow a teljes életciklusa alatt minden hivatkozott DataSource esetén egy kapcsolat objektumot tart nyilván és nyújt tartalmazott entitásai számára. A validateCustomerData metódus működése egyszerű: egy PreparedStatement-en keresztül megnézi, a beérkező paraméterek létező ügyfelet írnak-e le. Hasonlóan járunk el a Cachier esetében is: public class Cachier extends SuperPresence { private Connection c; @Override public void plan() throws BrillienException { c = getConnection("CustomerDataConnector"); } public String deposit( String owner, String accountNumber, long amount, String currency ){ // do something ... return "done"; } public String load( String owner, String accountNumber, long amount, String currency ) throws BrillienException { PreparedStatement spst = null, upst = null; ResultSet r = null; try { spst = c.prepareStatement( "SELECT a.balance FROM account AS a WHERE a.id = ? AND a.owner_id in (SELECT c.id FROM client AS c WHERE c.\"name\" = ?);" ); spst.setString( 1, accountNumber ); spst.setString( 2, owner ); r = spst.executeQuery(); r.next(); Double balance = r.getDouble("balance") - amount; upst = c.prepareStatement( "UPDATE account AS a SET balance=? WHERE a.id = ?;" ); upst.setDouble( 1, balance ); upst.setString( 2, accountNumber ); if( upst.executeUpdate() == 0 ){ throw new BrillienException( "Unable to modify balance of " + accountNumber ); } } catch (SQLException e) { throw new BrillienException( e ); } finally{ try { r.close(); spst.close(); upst.close(); } catch (Exception e) { e.printStackTrace(); } } return "done"; } }
Ezzel el is készült alkalmazásunk finomítása, mely most már adatbázis-kapcsolaton keresztül fér hozzá ügyfelei adataihoz. A modul fordítható, publikálható. A kliens futtatásával ki is próbálhatjuk. Message response received::"Successful transfer" F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
42
Az átutalás sikeres.
Tutorial 6: Ajax-alapú prezentációs réteg fejlesztése Az Ajax (Asynchronous Javascript and XML) lényege, hogy több már meglévő technológia (XML, DOM, XMLHttpRequest, Javascript) ötvözésével lehetőség adódik arra, hogy aszinkron kommunikáción keresztül adatokat cseréljek a böngészőn keresztül, az oldal újratöltése nélkül. Az xmpp szabvány egyik legfontosabb kiterjesztése a Bosh, mely lehetővé teszi, hogy http fölé egy plusz réteget emelve xmpp-alapú kommunikációt valósítsunk meg. Ezáltal egy web oldal is képes a Brillien presence-ekkel kommunikálni. Mostani példánk a creditBlue klienst fogja web-es platformra átemelni. Első lépés, hogy megteremtsük a megoldás technológia hátterét. 1.0 Bosh engedélyezése az xmpp szerverben. Ez ejabbard esetén a következőképpen néz ki: !
- a “mod_http_bind” modulnak engedélyezve kell lennie az ejabberd konfigurációs állományában:
{modules, [ ... {mod_http_bind, []}, ... ]}. - a HTTP szolgáltatások közé fel kell vennünk a http_bind és http_poll szolgáltatásokat: {listen, [ ... {5280, ejabberd_http, [ http_bind, http_poll, web_admin ] }, ... ]}.
!
- az XMPP szerver újraindításával a következő URL-en ellenőrizhetjük, hogy a Bosh szolgáltatás aktív-e:
!
!
http://prototype.brillien.org:5280/http-bind/
Amennyiben egy tájékoztató jellegű weboldal, és nem hibaüzenet fogad minket, működik.
2.0 Apache telepítés és konfigurálás !
Az ajax hívás sajátossága, hogy az aszinkron kérések csak ahhoz a szerverhez kerülhetnek továbbításra,
ahonnan a weboldal letöltődött. Így egy proxy funkcióra van szükségünk mely köztes pontként fog funkcionálni weboldalunk és az xmpp szerver között. A minden platformon elérhető apache-ot fogjuk használni, melynek konfigurációs állományába fel kell vennünk a következő sorokat: AddDefaultCharset UTF-8 Options +MultiViews RewriteEngine On RewriteRule http-bind/ http://prototype.brillien.org:5280/http-bind/ [P]
F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
43
Ez a weboldalról jövő minden “http-bind”-ot tartalmazó kérést továbbít az ejabberd felé. Apache webszerverünket (újra)indíthatjuk. !
Most, hogy minden technikai feltétel adott, megnézhetjük hogyan kell az egyszerű weboldalak és a Brillien
közötti kommunikációt megvalósítani. Első lépésként hozzuk létre a banki átutalás űrlapját:
Az űrlapon a mezők értékei megegyeznek a creditClient által küldöttekkel. Az oldal jobb alsó sarkában lévő gombra klikkelve kezdődhet meg az átutalás. A kommunikációról egy jquery alapú library, a strophe gondoskodik. Ezen tutorial html nevű alkönyvtárában található creditblue könyvtár tartalmazza a példa kódjait :
Az img alkönyvtár a felhasznált képeket, a js alkönyvtár pedig a javascript állományokat tartalmazza. A js tartalmazza a felhasznált jquery, strophe, json könyvtárakat és két saját javascript állományt is: brillien.js és transfer.js. A brillien.js a Brillien-alapú kommunikációhoz szükséges műveleteket (kapcsolódás, üzenetküldés, stb.) definiálja. A transfer.js az átutalással kapcsolatos műveleteket és struktúrákat definiálja. F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
44
A form működése egyszerű, az jobb alsó gombra klikkelve megkezdi az átutalást és a folyamatról log üzeneteket ír ki a képernyőre. Íme a végrehajtott kód: connect( '[email protected]', 'slinker', 10000, onConnect, on_message );
A kapcsolódáshoz egy usernév-jelszó páros, egy timeout és két callback handler van paraméterként felvéve. A callback handler-ek, melyek rendre az xmpp kapcsolat állapotváltozásait és bejövő üzeneteit fogadják, a transfer.js allományban kerültek definiálásra. Az onConnect működése: function onConnect(status) { if (status == Strophe.Status.CONNECTING) { log('Strophe is connecting.'); } else if (status == Strophe.Status.CONNFAIL) { log('Strophe failed to connect.'); } else if (status == Strophe.Status.DISCONNECTING) { log('Strophe is disconnecting.'); } else if (status == Strophe.Status.DISCONNECTED) { log('Strophe is disconnected.'); } else if (status == Strophe.Status.CONNECTED) { log('Strophe is connected.'); startTransfer ( ); } }
!
Amikor az xmpp kapcsolat felépül (status == Strophe.Status.CONNECTED), megkezdődik az átutalás a
startTransfer végrehajtásával. A strartTransfer funkció, a creditClient-hez hasonlóan, egyszerűen létrehoz egy TransferRequest objektumot, melyet a sendGet metóduson keresztül elküld a Brillien Transfer presence-ének. function startTransfer ( ) { var senderName = $("#sname").get(0).value; var senderAccount = $("#saccount").get(0).value; var recipientName = $("#rname").get(0).value; var recipientAccount = $("#raccount").get(0).value; var amount = $("#amount").get(0).value; var currency = $("#currency").get(0).value; sendGet( "[email protected]", 'SlinkerJS', "startTransaction", [ createTransferRequest( senderName, senderAccount, recipientName, recipientAccount, amount, currency ) ] ); }
A válasz fogadásáról az on_message függvény gondoskodik, mely a beérkező üzenetből kiszedi a válasz szövegét: function on_message(message) { var body = $.evalJSON( $(message).text().substring( 4 ) ); $('#log').append('').append(document.createTextNode( 'Received:: ' + body.response ) ); connection.disconnect(); return true; }
Ezzel meg is vagyunk a kliens oldali fejlesztéssel. Ezen tutorial html nevű alkönyvtárában található creditblue könyvtárat másoljuk az apache docroot könyvtárába, majd töltsük be az alkalmazás weboldalát: http://prototype.brillien.org/creditblue/transfer.html
A mezők kitöltését és az átutalás elindítását követően az alábbi eredmény látható: F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
45
Nincs külön prezentációs rétegbeli technológia a Brillien-ben, tekintve, hogy az üzleti réteget jelentő presence-ek szolgáltatásainak ajax-alapú elérése a brillien.js használatával meglehetősen egyszerű.
Tutorial 7: Servlet integráció !
Egy meglévő és működő rendszerrel való integráció nagyon fontos kérdéssé válhat a Brillien bevezetésekor.
Ennek megfelelően ezen tutorial egy aprócska példán keresztül mutatja be, hogyan kommunikálhat egy servlet és egy presence. A megoldás könnyedén alkalmazható web szolgáltatások, JSP illetve JSF oldalak, EJB vagy bármilyen más Java-alapú eszköz esetén is. A Brillien clarity, ignition és services moduljai biztosítják számunkra a legalapvetőbb kommunikációs eszközöket, mellyel a heterogén rendszer elemei közötti kommunikációs harmónia megteremthető. Míg clarity és ignition modulok a legfontosabb interfészeket és kommunikációs eszközöket (pl: BrillienClient) definiálják, addig a services, az ezekre épülő, integrálást elősegítő eszközöket biztosítja. Ezen modulokat fogjuk jelen példánkban használni. Rendszerünk az alábbi ábrának megfelelően kerül finomításra:
F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
46
Hozzuk létre creditWeb projektünket a következő Maven2 utasítással: mvn archetype:create -DgroupId=com.vii.brillien.tutorial -DartifactId=creditWeb -DarchetypeArtifactId=maven-archetype-webapp
Függőségként jelöljük meg a servlet API-t, az slf4j logging framework-öt és a Brillien services modulját: <dependency> javax.servlet <artifactId>servlet-api 2.5 <scope>provided <dependency> com.vii.brillien <artifactId>services [10.05-SNAPSHOT,) <scope>compile <dependency> org.slf4j <artifactId>slf4j-api 1.5.10 <scope>compile <dependency> org.slf4j <artifactId>slf4j-log4j12 1.5.10 <scope>compile
Vessünk egy pillantást a services modul com.vii.brillien.services.web csomagjára, mely web-alapú kommunikációkat segítő osztályok gyűjteménye. A számunkra fontos osztályok az alább ábrán láthatóak:
F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
47
A DispatcherServlet egy specializált absztrakt servlet osztály, melynek polimorfizált doPost metódusa JSON üzenetekkel kommunikál a kliensekkel. A JSONRequest osztály reprezentálja a fogadott, a JSONResponse a válaszként küldött üzenet formátumát. A működése egyszerű: minden bejövő JSONRequest által leírt hívásból XMPP hívást csinál, majd a művelet eredményét JSONResponse-ba csomagolva küldi vissza a kliensnek. A JSONRequest felépítése: public class JSONRequest { private String serviceName; private HashMap<String, Object> parameters; .. }
A JSONResponse felépítése: public class JSONResponse { private int responseCode; private String responseMessage; private Object payLoad; ... }
A DispatcherServlet tervezésekor fontos volt, hogy általános megoldást nyújtson és ne jelentsen megszorítást a web réteg kialakítására vonatkozólag. Ennek megfelelően a JSON üzenetformátumok mentesek minden Presence-ekkel kapcsolatos logikától. Ahhoz, hogy az üzenetet egy Presence-nek továbbítani lehessen, egy megfeleltetésre van szükség a JSONRequest és a Presence-ek között, melyet a Reference osztály hivatott megvalósítani. Minden bejövő üzenet, az adott serviceName-hez társított Reference által hivatkozott Presence-hez kerül továbbításra. A társítást a DispatcherServlet specializációjakor kell implementálnunk. A korábban is használt TransferRequest osztályt átvesszük és a következő servlet osztályt definiáljuk: public class ProxyServlet extends DispatcherServlet { private static HashMap<String, Reference> references; static{ references = new HashMap<String, Reference>(); references.put( "startTransaction", new Reference( "[email protected]", "startTransaction" ) ); } { timeout = 10000; } @Override protected BrillienClient getBrillienClient(HttpServletResponse httpServletResponse) throws XMPPException { if( this.getServletContext().getAttribute("BrillienHelper") == null ){ try { F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
48
BrillienClient client = new BrillienClient( "localhost", 5222, "prototype.brillien.org", "slinker", "slinker" ); this.getServletContext().setAttribute("BrillienHelper", client); } catch (XMPPException e) { reportError( httpServletResponse, e.getMessage() ); throw e; } } return (BrillienClient) this.getServletContext().getAttribute("BrillienHelper"); } @Override protected Reference getReference(String serviceName) { return references.get( serviceName ); } }
A DispatchingServlet specializációja két absztrakt metódus implementálását írja elő számunkra. A getBrillienClient biztosítja az XMPP kapcsolatot, míg a getReference metódus a serviceName-Reference megfeleltetésekért felelős. Az egyszerűség kedvéért egyszerű HashMap-et használunk a Reference-ek kezelésére. A fenti kódnak megfelelően adódik a kérdés: amennyiben a JSONReqeust HashMap-ben adja át az üzenet paramétereit, jelen esetünkben a TransferRequest objektumot, akkor hogyan lesz ebből XMPP üzenet? A Brillien a következő annotációt definiálja: @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) @Inherited public @interface P { String name(); }
Metódusok paraméterei mellé felvehető annotáció típus, melynek célja, hogy biztosítsa az adott paraméter nevének lekérdezhetőségét. Minden Presence a következő két üzenetfeldolgozó metódussal rendelkezik: List<String> getMessageProcessorParameterNames( BrillienException; List<String> getMessageProcessorParameterTypes( BrillienException;
String
messageProcessorName
)
throws
String
messageProcessorName
)
throws
Egy Presence, a “getMessageProcessorParameterNames” üzenet hatására megmondja, hogy a paraméterként megadott üzenetnév
feldolgozásáért
felelős
metódus
milyen
nevű
paraméterlistával
rendelkezik.
A
getMessageProcessorParameterTypes üzenetre, ennek megfelelően a paraméterlista típusainak neveit tartalmazó válasz kerül visszaküldésre. Röviden, egy Presence megmondja, hogy egy adott üzenetet hogyan kell ellátni paraméterekkel, egyfajta kompatibilitási vagy harmonizációs szolgáltatást biztosítva, megteremtve a HashMap, formális paraméterlistára történő leképezés lehetőségét. A BrillienClient osztály sendX metódusai HashMap-et is elfogadnak a paraméterek átadására, majd a címzett Presence-el egyeztetve alakítják a Presence által elvárt paraméterlistává. Ennek előfeltétele, hogy az adott üzenetet feldolgozó metódus paraméterei a Presence-en belül megfelelően el legyenek látva @P típusú annotációval, ahogy az alábbi példa is mutatja: public String startTransaction( @P(name="req") TransferRequest req ) throws BrillienException { ... }
Megjegyzés: célszerűen minden Presence esetén így kell definiálni az üzenetfeldolgozó metódusok paraméterlistáit. A creditBlue projektünk újrabuildelését követően, a creditWeb projekt is buildelhető. !
Jelen példa a Tomcat 6.0.24-et fogja servlet konténerként használni. A telepítést követően, a konténer webapps
könyvtárba másoljuk a mvn package műveletet követően előálló war állományt. F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
49
Hozzuk létre a http post-ot küldő kliensünk projektjét: mvn archetype:create -DgroupId=com.vii.brillien.tutorial -DartifactId=creditWebClient -DarchetypeArtifactId=maven-archetype-quickstart
Függőségként a pom.xml-ben megjelenik a Brillien streamline modulja (JSON kezeléshez), az slf4j logging framework, valamint apache http client, mely a post küldéséért lesz felelős. <dependency> org.apache.httpcomponents <artifactId>httpclient 4.0 <scope>compile <dependency> com.vii.brillien <artifactId>services [10.05-SNAPSHOT,) <scope>compile <dependency> org.slf4j <artifactId>slf4j-api 1.5.10 <scope>compile <dependency> org.slf4j <artifactId>slf4j-log4j12 1.5.10 <scope>compile
A korábban is használt TransferRequest osztályt egyszerűen átmásoljuk és a következő Sender osztályt definiáljuk: public class Sender { public static void sendTranser(TransferRequest request) throws Exception { JSONRequest jsonRequest = HttpServices.getRequest( "startTransaction" ); jsonRequest.getParameters().put( "req", request ); String message = JsonServices.toJSON( jsonRequest ); HttpClient httpclient = new DefaultHttpClient(); HttpPost post = new HttpPost("http://localhost:8080/creditWeb/ProxyServlet"); StringEntity myEntity = new StringEntity( message,"UTF-8"); post.setEntity( myEntity ); HttpResponse response = httpclient.execute( post ); HttpEntity entity = response.getEntity(); if (entity != null) { System.out.println("Received::" + EntityUtils.toString(entity) ); }
}
} public static void main(String[] args) throws Exception { TransferRequest request = new TransferRequest(); sendTranser( request ); }
F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
50
A kliens az apache commons httpclient segítségével egy post üzenetben küldi a servlet-nek a JSONRequest-be ágyazott TransferRequest objektumot, majd a JSONResponse típusú választ kiíratja a képernyőre. A válasz a JSONResponse payload tagjában található. Ezt követően buildelhetjük projektünket, és futtathatjuk. A képernyőn megjelenő eredmény: Received::{"payLoad":"Successful transfer","responseCode":0,"responseMessage":null}
A fenti ismertetett megoldás könnyedén alkalmazható JSP, JSF, Web Services vagy bármilyen más Java-alapú rendszer esetén is.
Tutorial 8: Rendszerfolyamatok kezelése Egy üzleti alkalmazásban szükség lehet rendszerfolyamatok alkalmazására, melyek a háttérben folyamatosan futva felügyeleti, validációs vagy egyéb feladatokat látnak el. Egy presence kommunikációk indítását leíró viselkedését az Aspirer interfész definiálja. Eddig csak a send metódusokat használtuk, ahol az indított kommunikációk más presence-ek felé irányúltak. Az Aspirer egy saját, belső kommunikációt is definiál, mely független a külső, más presence-ekkel folytatott kommunikációktól. Jelen példánkban is rendelkezésre állhatna egy olyan presence, mely nem külső kommunikációk hatására mutat valamilyen viselkedést, hanem belső működéssel rendelkezik, például folyamatosan ellenőrizi a számlák adatai konzisztenciáját, negatív számlaegyenleget keresve. Jelen projektünkben egy CreditSuperVision presence-t definiálunk az alábbi ábrának megfelelően:
A CreditSuperVision folyamatosan ellenőrzi a bank számláit, negatív egyenleggel rendelkezőt keresve. A megtalált negatív egyenlegű számlák adatait naplózza. @PresenceService( logLevel = PresenceService.INFO, timeMeasure = 2000, periodical = true ) @Resident public class CreditSuperVision extends SuperPresence { private Connection c; @Override public void plan() throws BrillienException { F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
51
c = getConnection("CustomerDataConnector"); } @Override protected R innerCall() throws BrillienException { log( "Examining accounts..." ); try { StringBuilder sb = new StringBuilder(); PreparedStatement st = c.prepareStatement( "SELECT id FROM account where balance < 0" ); ResultSet s = st.executeQuery(); while( s.next() ){ sb.append( " " + s.getLong("id") ); } s.close(); st.close(); log( "Accounts with negative balance: " + sb.toString() ); } catch (SQLException e) { exceptionLog( CreditSuperVision.class.getName(), "innerCall", e ); } return null; }
}
A @PresenceService annotáció számos paraméterezési lehetőséget nyújt, többek között a presence-ek belső kommunikációjának aktiválására és szabályzására. A timeMeasure felvétele jelzi a Brillien számára, hogy a presence - a periodical = true érték megléte esetén periodikusan - egyébként egyszeresen végrehajtandó. Periodikus végrehajtás esetén a timeMeasure a végrehajtások periódusát -, míg egyszeri végrehajtás esetén a végrehajtás idejének felső korlátját jelenti. A CreditSuperVision típusból csak egy példány készül a @Resident annotációnak megfelelően. A timeMeasure = 2000, periodical = true írják elő, hogy a példány 2 másodpercenként periodikusan fusson le, mely az innerCall metódus meghívását jelenti. Az innerCall minden lefutáskor naplózza a megtalált negatív egyenleggel rendelkező számla azonosítóit. Megjegyzés: A Brillien ennek végrehajtását külön szálban végzi, függetlenül a külső kommunikációk végrehajtási szálaitól. Projektünket kipróbálhatjuk, a Brillien naplójában megjelennek a következő sorok a fent definiált gyakoriságnak megfelelően: … INFO: Examining accounts... … INFO: Accounts with negative balance:
692875732
Tutorial 9: Exception handling Eddigi példák “happy case” elvet követtek, vagyis az osztálydefiníciók nem tartalmaztak semmilyen hibakezelést csak a helyes működéshez tartozó kódot. Készítsük fel creditBlue alkalmazásunkat a folyamat során esetlegesen bekövetkező hibákra. A tutorial néhány példán keresztül demonstrálja, hogyan viselkedik egy presence kivételes helyzetben, mi történik kommunikációival, hogyan kezelhetők a nemvárt szituációk. 1. Rossz típusú input Próbáljunk meg kliensünkkel TransferRequest-től eltérő típusú objektumot küldeni a Transfer presence számára: String msg = sendGet( F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
52
10000, "transfer", "startTransaction", new String("How can you deal with this?") ).getResponse();
Futtatáskor egy kivétel kapunk: Exception in thread "main" com.vii.brillien.kernel.BrillienException: remote-server-error(1000) Error while calling messageProcessor method:startTransaction
A Brillien egy kommunikáció során bekövetkezett kivétel esetén a kivétel üzenetét küldi vissza a feladónak mint választ. A sendGet metódus a választ megkapva, kivétellel jelzi, hogy a szerver oldalról megkapott válasz egy hibaüzenet. Ez így működik rossz paraméterátadáskor, sikertelen paraméterkiértékeléskor, valamint a hívott presence működésében bekövetkező kivétel esetén is. A startTransaction üzenet hatására elindult folyamat természetesen leáll és a couchDB-ben “Failed” státusszal szerepel. A Brillien beépített ReferenceRetriever presence-e folyamatosan ellenőrzi a futó flow entitásokat, lezárható folyamatokat keresve. Lezárhatónak tekinthető egy folyamat, ha az adott folyamat nem rendelkezik lezáratlan kommunikációval, minden entitása rendelkezik EXIT állapottal, vagy van legalább egy olyan entitás, mely rendelkezik ERROR állapottal. Utóbbi esetén minden ERROR állapot neve is elmentésre kerül. Ezekből következik, hogy egy folyamaton belül nem rendelkezhet egyetlen presence sem ERROR típusú állapottal, különben a ReferenceRetriever beszünteti működését. A lezáratlan kommunikációkra vonatkozó feltétel szükséges, hiszen hiba esetén minden válaszra várakozó entitást értesíteni kell a fennálló hiba tényéről. A folyamat csak ezt követően kerülhet leállításra. 2. Nem megfelelő tartalommal rendelkező input A Brillien rendelkezik a Validable nevű beépített interfésszel, melynek egyetlen checkValidity metódusa kivétellel jelzi, ha az adott típus valamilyen szempontból nem megfelelő. Egy presence kommunikációja során, minden kapott, Validable interfészt implementáló paraméter esetén végrehajtódik a checkValidity metódus, és csak a sikeres végrehajtásukat követően kerül értesíti a Brillien a címzett entitást az üzenet érkezéséről. Az esetlegesen bekövetkező kivétel a kommunikáció feldolgozásának a végét, és a kivétel üzenetének a hívó felé válaszként történő küldését jelenti. Röviden, a Brillien minden arra felkészített típusú paraméter esetén automatikusan ellenőrzi annak helyességét. Most küldjünk a klienssel egy hiányosan feltöltött TransferRequest objektumot, mellyel a tranzakció nem végrehajtható. A TransferClient osztály request.setRecipientAccountNumber("69287554532") sorát request.setRecipientAccountNumber( null )-ra cserélve hiányos TransferRequest objektumot tudunk a Transfer számára elküldeni. A szerver oldalt lévő TransferRequest típust módosítsuk a következőknek megfelelően: public class TransferRequest implements Validable { … public void checkValidity() throws BrillienException{ if( senderName == null || senderAccountNumber == null || recipientName == null || recipientAccountNumber == null || amount < 1 || currency == null || !currency.equals("Euro") ) throw new BrillienException("Invalid TransferRequest field(s)!"); } }
A kódot futtatva a következő kivétel váltódik ki: Exception in thread "main" com.vii.brillien.kernel.BrillienException: remote-server-error(1000) Invalid TransferRequest field(s)!
Megjegyzés: Célszerű minden kommunikációban paraméterként használt saját típusnál implementálni a Validable interfészt.
F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
53
Megjegyzés2: A kliens oldalon szükségtelen a Validable interfész, hiszen JSON formátumban kerülnek az adatok továbbításra. 3. Belső hiba Állítsuk vissza kliensünket, hogy megfelelő TransferRequest-et küldjön és módosítsuk a Monopolite kódját, hogy sikertelen végrehajtást küldjön vissza az Exchanger presence-nek: public String arrange( String sender, String recipient, String accountNumber, long amount, String currency ){ System.out.println("Arrangement request received from " + sender + " to " + recipient + " at " + accountNumber + " with " + amount + " " + currency); return "Cannot accomplish the request"; }
Egy ilyen választ megkapva, az Exchanger entitás a végrehajtás alatt lévő folyamatot be kell, hogy fejeztesse. Erre használatos az panic metódus: public class Exchanger extends SuperPresence { public void externalize( String sender, String recipient, String accountNumber, long amount, String currency ) throws BrillienException { sendAGet( "arranger", "arrange", "done", sender, recipient, accountNumber, amount, currency ); flow.suspend(); } public String done( String message ){ if( !message.startsWith("Success") ) panic( "externalization failed" ); System.out.println("Transaction done."); return message; } }
Az panic feladata, hogy egy szöveges üzenettel jelezze a presence szabálytalan működését. Meghívása esetén a presence állapotai közé egy ERROR-típusú állapot kerül felvételre, mely hordozza a hiba üzenetét. Az eseményt kiváltó, beérkező kommunikáció feladójának egy hibaüzenet jelzi a probléma létrejöttét és a ReferenceRetriever következő lefutásakor, amennyiben nincs függőben lévő kommunikáció, a tartalmazott folyamat “FAILED” státusszal befejeztetésre kerül. A couchDB-ben az adott folyamat dokumentumának failureMessage mezője hordozza a hibaüzenetet. A projekt buildelését és futtatását követően az alábbi output jelenik meg kliens oldalon: Exception in thread "main" com.vii.brillien.kernel.BrillienException: remote-server-error(1000) , response='"externalization failed"'}
…
Illetve megtekinthetjük a couchDB-ben megjelenő dokumentumot:
F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
54
4. Timeout-ok kezelése A presence-ek közötti kommunikációk aszinkron volta miatt számos esettel számolni kell, például: - egy indított kommunikációra talán sohasem érkezik válasz - üzleti logika miatt egy folyamat időtartama felülről korlátos A Brillien a következő lehetőségeket biztosítja időkorlátok kezelésére: - sendGet metódusok: minden szinkron üzenetküldés rendelkezik egy, vagy paraméterként vagy alapértelmezett módon definiált timeout értékkel, melynek letelte kivétel kiváltódását eredményezi. Ezt használtuk minden eddigi tutorial kódban. TransferClient:: String msg = sendGet( 10000, "transfer", "startTransaction", request ).getResponse(); Transfer:: sendGet( "customerdataprovider", "validateCustomerData", req.getSenderName(), req.getSenderAccountNumber() ).getResponse( )
A creditClient legfeljebb 10 másodpercet vár a válaszra, míg a Transfer alapértelmezett értéknek megfelelő ideig várja a Cachier és CustomerDataProvider
entitásoktól érkező választ. Az alapértelmezett időkorlát a Brillien services.xml
konfigurációs állományában a <packetReplyTimeout> tag-ben kerül definiálásra. - flow timeout: az @Activate annotáció, mely egy folyamat elindításáért felelős, szintén rendelkezik timeout értékkel. Megadása esetén az adott folyamatnak be kell fejeződnie az időkorlát lejárta előtt. Az időkorlát túllépésekor, amennyiben a folyamat még nem fejeződött be, a Brillien meghívja az adott presence public void aspirationTimeIsUp()
metódusát, mely alapértelmezett módon csak naplózást végez. A Transfer esetén a következőképpen néz ki: public class Transfer extends SuperFlow { …. @Activate( timeout = 8000 ) public String startTransaction( TransferRequest req ) throws BrillienException { … } @Override public void aspirationTimeIsUp() { System.out.println("Working too hard..."); } …. F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
55
}
Megjegyzés: Üzleti logikától függően, polimorf metódusa félbeszakíthatja a folyamat végrehajtását az oppress metódus meghívásával. Megjegyzés2:
ugyanezen
aspirationTimeIsUp
metódus
hajtódik
végre
akkor
is,
amikor
egy
entitás
belső
kommunikációjának egyszeri végrehajtása túllépi a @PresenceServices annotációban megadott timeout értéket. - üzenet elévülési idő: minden presence típus definíciójának @PresenceService annotációjában, messageExpiration néven definiálható egy érték, mely minden hozzá beérkező üzenet érvényességi idejét definiálja. Amennyiben a beérkező üzenet feladási ideje korábban volt, mint a beérkezés időpillanata mínusz a megadott érték, az üzenet eldobásra kerül. Röviden, a kellően régi üzeneteket az adott presence figyelmen kívül hagyja. Transfer entitásunknál így néz ki: @PresenceService( logLevel = PresenceService.INFO, messageExpiration = 2000 ) @Indispensable @Sparkle public class Transfer extends SuperFlow { … }
Tutorial 10: A Processor presence A korábbi tutorial-ok mind statikus viselkedésű presence-ek használatára mutattak példákat. A presence-ek, viselkedésük Java nyelven történő megírását, a tartalmazó modul fordítását és csomagolását követően kerültek publikálásra. Vizsgáljuk meg a következő funkciókat: - a bank, ügyfelei számára lehetővé teszi, hogy folyószámlájukhoz saját műveleteket rendeljenek. Például minden adott összegnél magasabb levonásnál küldjön értesítést. vagy céltartalékképzésként minden hónapban kössön le X összeget, stb. Ügyfél saját céljainak megfelelően definiálhatná, módosíthatná a számláira vonatkozó műveleteket. - a pénzügyi törvényeknek megfelelően vizsgálná a bank belső pénzmozgását, hogy az esetleges visszaéléseket észlelje és jelentse. Természetesen a vonatkozó törvények hatálya, rendelkezései időben erősen változhatnak. Röviden: egy folyamatosan üzemelő rendszer egy-egy elemének viselkedése időben változást mutathat, környezetének megváltozásának következtében, más szóval kintről lehet egy presence működését vezérelni. Ez az eddig ismertetett entitásokkal ez megoldhatatlan lenne. !
A Processor egy speciális presence a Brillien-ben, melynek viselkedését ki lehet terjeszteni javascript nyelven
megírt kódokkal. Működését az alábbi ábra szemlélteti:
Egy “assume” üzenetet tartalmazó kommunikáció indításával script kódot lehet egy Processor típusú presence-be injektálni, mely az új viselkedését specifikálja. A presence példány minden bejövő kommunikáció esetén az üzenet feldolgozásához szükséges metódust először script kód alapján próbálja feloldani, amennyiben ilyen létezik. Megfelelő üzenetfeldolgozó script függvény hiányában a normál presence-ekre jellemző módon történik meg az üzenet feldolgozása. F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
56
Terjesszük ki CreditBlue alkalmazásunkat azzal a funkcióval, hogy minden, egy adott összeget meghaladó átutaláskor, naplózza az átutalás adatait! Természetesen egy valós banki alkalmazás jóval komplexebb és - biztonsági okokból korlátozottabb megoldást alkalmazna, csak a tutorial egyszerüsének megtartása végett tesszük lehetővé ily módon a banki alkalmazás viselkedésének módosítását. Ez a tutorial csak demonstrálja a kód injektálást, ám könnyen kiterjeszthető jóval komplexebb üzleti logika megvalósítására. !
Most gondoljuk végig, hogyan is használható egy ilyen presence! Egy alkalmazásom logikai hálójában
szükségem lenne néhány ponton dinamikus viselkedést mutató Processor entitásra. Figyelembe véve, hogy
ezen
presence típus lényege, hogy viselkedését kívülről érkező script-ek határozzák meg, nem feltétlenül válik szükségessé az osztály specializációja. Ez azt jelenti, hogy a Processor presence típus megjellene az alkalmazás több pontján is eltérő funkcionalitással. Egy presence publikálása osztálydefinícióhoz volt eddig kötve, ami azt követelné meg a fejleszőtől, hogy üres alosztályokat definiáljon jelen példában. A Brillien megengedi, hogy egy osztálydefiníció több néven is publikálásra kerülhessen. Ez ténylegesen különböző entitásokat fog jelölni, különböző néven és példányokkal, csak az osztálydefiníciójuk fog megegyezni. A Brillien klónoknak nevezi ezeket. Az creditBlue alkalmazásunk az alábbiaknak megfelelően finomodik:
A Reporter új “klón” entitás definiálásához írjuk be a következő sorokat a creditBlue modul activate.xml állományába: <deployment> ... <presence> Processor Reporter
Ez a bejegyzés egy klóngyártást ír elő a Brillien számára, miszerint a Processor entitásból egy Reporter nevű entitást gyártson a Processor osztálydefiníciójának felhasználásával. Így a modul publikálásakor a Brillien új entitást definiál Reporter néven, melyet megtekinthetünk az ejabberd felhasználói fióklistájában és a Brillien naplózási adatai között is: “Domo by reporter”
Ám publikálás előtt állítsuk vissza az Arranger presence eredeti működését, hogy az átutalás folyamata lefusson: F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
57
public String arrange( String sender, String recipient, String accountNumber, long amount, String currency ){ System.out.println("Arrangement request received from " + sender + " to " + recipient + " at " + accountNumber + " with " + amount + " " + currency); try { Thread.sleep( 1500 ); } catch (InterruptedException e) { e.printStackTrace(); } return "Successful"; }
A Cachier load metódusába vegyük fel a következő sort, közvetlenül a “return "done";” sor elé: sendSet( "Reporter", "reportLoad", owner, accountNumber, amount, currency );
Ez minden átutalásnál egy “reportLoad” üzenetet küld a klón Reporter presence-nek, mely alapértelmezett módon nem fog kezdeni vele semmit sem, hiszen nem rendelkezik még az üzenet feldolgozásához szükséges viselkedéssel, az általános processMessage üzenetfeldolgozó metódusa fogja az üzenet feldolgozását elvégezni. A szükséges szerver oldali módosításokat elvégeztük, a creditBlue modul publikálható. Kliens oldalon vegyük fel a creditClient modul gyökerében a report.js állományba a következő sorokat: function reportLoad( owner, accountNumber, amount, currency ){ if( amount > 500 && (currency.toLowerCase() == 'Euro'.toLowerCase()) ){ print( '!!! Load to Report::' + owner + ' ' + accountNumber + ' ' + amount + ' ' + currency ); } }
Vegyünk fel a creditClient modulba egy új CustomReport osztálydefiníciót: public class CustomReport extends BrillienClient { public CustomReport(String address, int port, String xmppServerName, String username, String password) throws XMPPException { super(address, port, xmppServerName, username, password); } public void assumed( String answer ){ System.out.println(" Assumed: " + answer ); } public void sendRequest() throws Exception { sendAGet( "reporter", "assume", "assumed", IOServices.getStreamAsString( IOServices.getInputStream("report.js") ) ); } public static String startActing( String hostName, int port, String domainName, String userName, String password ) throws Exception { CustomReport client = new CustomReport( hostName, port, domainName, userName, password ); client.sendRequest(); return "Sent."; } public static void main(String[] args) throws Exception { System.out.println(" " + startActing( "localhost", 5222, "prototype.brillien.org", "slinker", "slinker" ) ); } }
A publikált és futó creditBlue mellett futtassuk le a CustomReport main metódusát, melynek végrehajtását követően a következő output látszik: “ Sent.” ---- CustomReport kimenete F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
58
FINER: Evaluating script:function reportLoad( owner, accountNumber, amount, currency ){ print( '!!! Load to Report::' + owner + ' ' + accountNumber + ' ' + amount + ' ' + currency ); if( amount > 1000 && (currency.toLowerCase() == 'Euro'.toLowerCase()) ){ print( '!!! Load to Report::' + owner + ' ' + accountNumber + ' ' + amount + ' ' + currency ); } } ------ részlet a szerver naplójából
Ez a kódbeágyazás sikerességét jelenti. Most futtassuk le a TransferClient main metódusát, mely az átutalást kezdeményezi. A kliens lefutása a következő kimenetet eredményezi: “ Successful transfer” ---- TransferClient kimenete “!!! Load to Report::John Smith 692875732 1000 Euro” ---szerver oldali kimenet
A futási eredmények jól demonstrálják, hogyan változott meg a Reporter presence működése futás közben, javascript kód beágyazásával. Ez az eljárás könnyedén átültethető bármilyen dinamikus viselkedést mutató presence-ek megvalósítását igénylő problémára.
Tutorial 11: Környezetek használata !
Egészítsük ki alkalmazásunkat egy új funkcióval, mely az átutalások felügyeletét hivatott megvalósítani.
Minden átutalás során, ha az átutalás összege meghalad egy kritikus szintet, a banki szabályzásnak megfelelően, értesíteni kell a bank pénzügyi felügyeletét a bonyolított ügyletről. Vizsgáljuk meg mit is jelent a fent leírt feladatspecifikáció a powerobject modell környezet fogalmánál taglalt tanszéki példán keresztül. Egy oktató, mely eleme a tanszéki környezetnek, szintén eleme a kari és egyetemi környezetnek is, ez következik a környezet halmaz tulajdonságából. Egy presence akárhány halmazban lehet tag, melyekre ez az elv szintén teljesül, így a környezetek nagyon bonyolult struktúrát alkothatnak. Egy oktató lehet több tanszék oktatója is, több egyetemen is akár, stb. Így egy presence, mely eleme minden környezetnek mely közvetve vagy közvetetten tartalmazza őt, eléri azok összes megosztásait. Egy tanszék oktatója írhat kérelmet a kar dékánjának vagy az egyetem rektorának is. Korlátlan sok példát tudunk hozni erre mindennapi életünkből. !
Bank példánkkal ezzel teljesen analóg, hiszen a bankon, mint környezeten belül a “pénzügyi felügyelet” egy
létező részleget jelöl, melynek a banki környezetben betöltött szerepe válik igazán fontossá ezen “közös terében” ténykedő entitások számára. Megjegyzés: A powerobject fogalmainak megfelelően, eddig használt folyamataink környezetként is funkcionáltak a tartalmazott presence-ek számára, de eddig ezen tulajdonságát nem használtuk ki. Vegyük fel a következő presence definíciót: @PresenceService @Resident public class Supervisor extends SuperPresence { public void investigate( String owner, String accountNumber, long amount, String currency ){ System.out.println("Investigating about : " + owner + " " + accountNumber + " " + amount + " " + currency ); } }
A Supervisor presence feladata, hogy a “pénzügyi felügyelet” szerepét betöltse. A kivizsgálandó átutalás adatait az investigate üzeneten keresztül lehet elküldeni számára. A creditBlue rendszerünk egy bank pénzügyeinek kezelését hivatott megvalósítani. Ezen rendszeren belül egyetlen egy “pénzügyi felügyelet” részleg létezik, így a példányosítási mód @Resident lett. Most definiáljuk a bankot, mint környezetet: F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
59
@PresenceService @Resident public class Bank extends AbstractContext { { registerSharing( this, "monetarySupervision", "Supervisor" ); } }
Ezen
presence
példányosításakor,
mely
automatikusan
megtörténik
publikációjakor,
a
Supervisor
presence
monetarySupervision néven kerül megosztásra a banki környezetben. Ez minden presence számára biztosítja a Supervisor monetarySupervision néven keresztüli elérését. Finomítsuk Cachier presence-ünk load metódusának definícióját az alábbiaknak megfelelően: public String load( String owner, String accountNumber, long amount, String currency ) throws BrillienException { ... if( amount > 1000000 && currency.equals("Euro") ){ sendSet( ":Bank:monetarySupervision", "investigate", owner, accountNumber, amount, currency ); } return "done"; }
A módosításnak megfelelően, minden átutalás esetén, amennyiben az összeg meghaladja az egy millió euro-t, a Cachier jelentést tesz a bank monetarySupervision entitásánál. A Cachier nem szabad és nem is kell, hogy tudja milyen entitást takar a név, hiszen csak annyi feladata, hogy a vizsgálatot igénylő átutalásról jelentést tegyen a banki környezet monetarySupervision szerepet betöltő entitásánál, bárki is legyen az. Eddigi példák során a setX metódusokban címzettként konkrét entitások neveit adtuk meg, ám lehetőség van közvetett hivatkozásra is, ahol a hivatkozás feloldása a hívást indító presence-t tartalmazó környezetek feladata. A hivatkozás minden esetben “:”-al kezdődik, majd egy útvonal írja le hogyan érhető el az adott presence neve, melyre a kommunikáció továbbításra kerül. A :Bank:monetarySupervision azt jelenti, hogy a Cachier presence saját banki környezete monetarySupervision szerepű presence-ének küld üzenetet. Az útvonal feloldásának folyamata: az útvonal első eleme, a presence környezetében elérhető megosztást kell, hogy jelentsen, mely jelentheti a közvetlen vagy bármely tartalmazó környezet megosztását is. Amennyiben a megosztott objektum egy halmaz vagy környezet, az útvonal további elemei az adott struktúrán belül navigációt jelentik a tartalmazott presence-ek -, illetve megosztások neveinek megfelelően. Az útvonal utolsó eleme egy presence neve kell, hogy legyen kötelező módon. Bármilyen névfeloldási probléma egy kivétel kiváltódását vonja maga után. A TransferClient kódját is finomítani kell, hogy megfelelő összegű átutalást kezdeményezzen: request.setAmount( 2000000 ); request.setCurrency("Euro");
Mostmár buildelhetjük és publikálhatjuk a creditBlue projektet, majd a kliens futtatásával az alábbi sort láthatjuk a szerver naplózási adatai között: Investigating about : John Smith 692875732 2000000 Euro
Kliensünk átutalása ezzel sikeres.
F e l h a s z n á l ó i k é z i k ö n y v!
Modellezés és fejlesztés
60