1 Budapesti Műszaki és Gazdaságtudományi Egyetem WEBFEJLESZTÉS ÚJ UTAKON Tudományos Diákköri Dolgozat Készítették: Farkas Gábor Gáthy Lajos Szabó Marc...
WEBFEJLESZTÉS ÚJ UTAKON Tudományos Diákköri Dolgozat
Készítették: Farkas Gábor Gáthy Lajos Szabó Marcell IV. évf. műszaki informatikus hallgatók
Konzulensek: Dr. Gajdos Sándor adjunktus Budapesti Műszaki és Gazdaságtudományi Egyetem Távközlési és Médiainformatikai Tanszék
Golda Bence PhD. hallgató Eötvös Loránd Tudományegyetem Informatikai Kar
2006. október 27.
Absztrakt Ma az egyedi igények kielégítésére készülő alkalmazások egyre nagyobb része webes technológiákon alapul. Ezekben az esetekben a fejlesztés költségeit egyetlen megrendelő viseli, az eladási ár és a versenyképesség kialakulásában döntő szerepe van a fejlesztés hatékonyságának. Miközben az egyedi alkalmazásokat ma is gyakran alacsony szintről indulva építik fel, számos keretrendszer és eszköz is létezik, amelyek a jellegzetes feladatokra általános, robusztus megoldást biztosítanak. Munkánkban újszerű, hatékonyságot növelő lehetőségeket elemeztünk, majd azok használhatóságát vizsgáltuk egy konkrét, de mégis tipikusnak mondható feltételrendszer mellett, majd pedig egy tényleges rendszerimplementáció során. Kiindulási alapnak tekintettük a tradicionális négyrétegű felépítést (adatmodell, üzleti logika, vezérlés, megjelenítés). Mindegyiknél több elterjedt megoldás lehetőségét vizsgáltuk, majd egy kiválasztott módszerrel meg is valósítottuk az adott réteget. A választás során az elsődleges szempontok a megoldás robusztussága, rugalmassága és a fejlesztés hatékonysága voltak, azonban nem feledkeztünk meg arról sem, hogy adott konkrét feladathoz is jól illeszkedjen. Az alsóbb rétegekben a Java Enterprise Edition 5.0 által biztosított objektumperzisztenciára és objektumkezelésre esett a választásunk. Az üzleti logika az Enterprise JavaBeans 3.0 specifikációjának megfelelő beanekből épül fel, ezeket egy Java EE kompatibilis alkalmazásszerver futtatja, ami modulárissá és skálázhatóbbá teszi az alkalmazást. Az alkalmazás fejlesztése során így már csak annak funkcionalitására kellett koncentrálni. A vezérlési rétegben több elterjedt webes alkalmazás-keretrendszer előnyeit és hátrányait vizsgáltuk (Spring MVC, Apache Struts, Millstone), majd a tapasztalatok alapján egy saját keretrendszert fejlesztettünk. Ez lehetővé tette, hogy a tényleges alkalmazás funkcionalitását deklaratívan, annak specifikációjával írjuk le. Ennek köszönhetően az alkalmazás fejlesztése átláthatóbbá, hatékonyabbá és könnyen dokumentálhatóvá vált. A megjelenítési rétegnél is egy újszerű koncepciót (az XAML, XUL mintájára) választottuk, ahol a megjelenés logikája teljesen függetlenített a többi rétegtől. Az adatok megjelenítéséhez használt transzformáció cserélhető és átírható a kliens oldalon is. A bemutatott elveken alapuló fejlesztés eredményességét egy elkészült, működő rendszer bizonyítja, a módszer és a választott eszközök hatékonysága pedig a fejlesztés során bizonyosodott be. Az elkészült alkalmazás a VIK Számítógép laboratórium VI. tantárgy oktatását fogja segíteni 2007. tavaszától.
Köszönetnyilvánítás Mindenek előtt köszönetet szeretnénk nyilvánítani mindazoknak, akik segítsége nélkül ez a dolgozat nem jöhetett volna létre. Köszönjük Dr. Gajdos Sándornak, hogy tantárgyi keretek között lehetőséget adott nekünk tudományos munka végzésére, szakmai fejlődésre, és hogy nagyban elősegítette dolgozatunk kialakulását. Köszönjük Golda Bencének a biztatását, szakmai észrevételeit és tanácsait, a dolgozatunkkal kapcsolatos lelkes segítségét, az erre fordított rengeteg időt és energiát. Köszönjük Benedek Zoltánnak, hogy Farkas Gábor az Automatizálási és Alkalmazott Informatikai Tanszéken önálló labor keretei között foglalkozhatott a kutatással. Köszönjük a Számítástudományi és Információelméleti Tanszéknek, hogy infrastruktúrájával segítette fejlesztői munkánkat. Köszönjük Tóth Dánielnek a megjelenítési réteggel kapcsolatos tanácsait, és hogy rendelkezésünkre bocsátotta a [36] és [37] irodalmakat.
Tartalomjegyzék ABSZTRAKT
2
KÖSZÖNETNYILVÁNÍTÁS
3
TARTALOMJEGYZÉK
4
1.
6
BEVEZETŐ 1.1.
MOTIVÁCIÓNK
6
1.2.
A CÉL ÉS AZ EREDMÉNY
6
1.3.
A DOLGOZAT FELÉPÍTÉSE
6
2.
CÉLKITŰZÉS, TECHNOLÓGIA-VÁLASZTÁS
7
2.1.
A PROBLÉMAOSZTÁLY
7
2.2.
A PÉLDAALKALMAZÁS
7
2.3.
TECHNOLÓGIA-VÁLASZTÁS
8
2.4.
WEBES ALKALMAZÁS TECHNOLÓGIÁK
9
2.4.1. 2.4.2. 2.4.3.
PHP ASP.Net Moodle keretrendszer
9 10 10
2.5.
JAVA
11
2.6.
JAVA ENTERPRISE EDITION
11
2.6.1. 2.6.2.
3.
A Java EE alkalmazásszerver A Java EE különböző verzióiról
A MEGVALÓSÍTÁSI SÉMA 3.1.
3.2.
Tárolt eljárások Object-Relational Mapping A Hibernate rendszer bemutatása Az adatmodell réteg implementációjának bemutatása
14 15 16 19 20 22 24 26
A technológiáról Az implementációról A konfiguráció
A beanekről Az alkalmazásszerverről részletesebben A Java EE 5.0 újításairól
ELSŐ RÉTEG: AZ ADATMODELL
3.2.1. 3.2.2. 3.2.3. 3.2.4. 3.3.
13
A JAVA EE PLATFORM RÉSZLETES BEMUTATÁSA
3.1.1. 3.1.2. 3.1.3.
12 12
32
A példaalkalmazásunk Nem strukturált végrehajtás Szekvenciális szétválasztás JSP bevezetése MVC Felhasználói interakciók Apache Struts Tapestry Millstone
32 33 34 35 36 37 37 39 42
4
3.4.10. 3.5.
50
Programozás a keretrendszerben Kapcsolódás az üzleti logikához Kapcsolódás a megjelenítéshez
A MEGJELENÍTÉS MEGVALÓSÍTÁSA
3.6.1. 3.6.2. 3.6.3. 3.6.4. 3.6.5. 3.6.6.
4.
45
A SAJÁT VEZÉRLÉS BEMUTATÁSA
3.5.1. 3.5.2. 3.5.3. 3.6.
Saját keretrendszer koncepciója
57
Általánosságban Absztrakt felhasználói felület definíciók Esettanulmány Webes felhasználói felület A megjelenítés a megvalósítási sémánkban Egy példa
TOVÁBBI LEHETŐSÉGEK 4.1.
4.2.
63
Eszközeink Lehetőségeink
63 64
A JAVA EE PLATFORM TOVÁBBI LEHETŐSÉGEI
4.2.1. 4.2.2. 4.2.3.
57 57 58 58 59 61
63
MODELLEZÉS LEHETŐSÉGEI
4.1.1. 4.1.2.
50 54 55
Vastagkliens alkalmazások Az alkalmazásszerver biztonsági mechanizmusai Az alkalmazásszerverek clusterbe szervezése
64 64 65 65
5.
ÖSSZEFOGLALÁS ÉS ÉRTÉKELÉS
67
6.
FÜGGELÉK
68
6.1.
CSOPORTMUNKA ESZKÖZÖK
68
6.2.
A PÉLDAALKALMAZÁS FEJLESZTÉSI PROJEKTJE
70
6.2.1. 6.2.2. 6.2.3. 6.2.4. 6.2.5. 6.2.6. 6.3.
A FUTTATÓKÖRNYEZET
6.3.1. 6.3.2. 6.4.
7.
Kiindulási anyagok, a korábbi rendszer Specifikáció Fogalomtár és use-case-ek Class Diagram Állapotgép Kliensalkalmazás
70 70 70 71 71 71 72
A JBoss alkalmazásszerver Adatforrás konfigurálása
72 73
EGY ÁLLAPOT XML
74
IRODALOMJEGYZÉK
76
5
1. Bevezető 1.1. Motivációnk Webes alkalmazásokra nagy igény van, ami az Internet terjedésével még tovább fog nőni. Ezen alkalmazások változatosak, bonyolultságuk is széles skálát ölel fel. Nagy számuk miatt a legegyszerűbb alkalmazásoknál is szükség van a fejlesztés hatékonyságát növelő technikákat alkalmazni, a legnagyobb alkalmazásoknál pedig a várható terhelés miatt lehetetlen sikeres alkalmazást készíteni kész komponensek és kialakult koncepciók nélkül. Mivel a dinamikus webes tartalom alig tíz éve vált elterjedtté, még nincsenek tradicionális, állandó, kiforrott paradigmák, mint a szoftverfejlesztés más területein. Egyre újabb és jobb ötletek merülnek fel, újabb programozási nyelvek, futtatási elvek készülnek. Sőt, maguk a webes szabványok is folyamatosan bővülnek, és a böngészők újabb kiadásai egyre újabb lehetőségeket nyitnak meg.
1.2. A cél és az eredmény Munkánk során azt kutattuk, hogy ma, 2006-ban, milyen lehetőségek állnak a webes alkalmazások fejlesztőinek rendelkezésére. Hogy a túlzott általánosságot leszűkítsük, kiválasztottunk egy konkrét, de kellően tipikusnak tekinthető problémaosztályt. A technológiai és módszertani lehetőségek elemzésekor az irányadó az volt, hogy az ilyen problémákat milyen hatékonyan, elegánsan lehet velük megoldani. Munkánk eredménye egy általános megvalósítási séma, melyben részben kiforrott technológiákra támaszkodtunk, részben saját megoldásokat vezettünk be. Ezt a sémát egy gyakorlati igényeket is kielégítő, működő alkalmazás kifejlesztésével is verifikáltuk.
1.3. A dolgozat felépítése Dolgozatunk két fő egységből áll. Az elsőben definiáljuk a problémaosztályt, melyre megoldást keresünk, és eldöntjük, hogy melyik technológiára alapozzuk majd a megvalósítási sémát. A második egységben a megoldási sémánkat mutatjuk be, külön egységeket szentelve az alkalmazás rétegeinek. Az egyes rétegekben röviden bemutatjuk a kínálkozó alternatívákat, megindokoljuk a döntésünket. Részletesebben bemutatjuk a kiválasztott alternatívát, vagy az alternatívák tapasztalatait felhasználó saját megoldást. Dolgozatunk végén a séma továbbfejlesztési lehetőségeivel foglalkozunk, majd az eredményeket összefoglaljuk és értékeljük. A függelékben a sémánk verifikálására választott alkalmazás projektjének menetét és eszközeit mutatjuk be.
6
2. Célkitűzés, technológia-választás Munkánk kiindulópontja egy problémaosztály. A problémaosztály jellemzői alapján egy általános alkalmazás-fejlesztési, megvalósítási sémát dolgoztunk ki, az elméleti érveket egy példaalkalmazás megvalósítása során verifikáltuk. Ez a fejezet a következő fejezetben tárgyalt elméleti kutatást készíti elő. Röviden jellemezzük a példa-alkalmazást, ám a következő fejezetben egy hasonló jellemvonásokat viselő, egyszerű, "állatorvosi ló" példát elemzünk. Hogy kutatásunk ne legyen túlzottan szerteágazó, a megvalósítási séma kidolgozásának első, nagyon meghatározó lépését, a technológia-választást ebben a fejezetben, a példaalkalmazásunk alapján hozzuk meg.
2.1. A problémaosztály A kitűzött problémaosztály egyes webes üzleti alkalmazások rétege. Egy ilyen alkalmazás egy valós életbeli folyamat-rendszert modellezzen. Az alkalmazás minden (absztrakt) objektuma egy-egy valós világbeli objektumnak vagy fogalomnak legyen megfeleltethető meg, és ezek interakcióit kövesse a rendszer. Továbbá kikötöttük, hogy a folyamatnak sok emberi résztvevője legyen, akik a világ tetszőleges számítógépéről el akarják érni a rendszert, viszont egyszerre csak kis mennyiségű adatot jelenítsenek meg – tipikusan egy, hozzájuk szorosabban kapcsolódó adathalmazon dolgozzanak.
2.2. A példaalkalmazás Dolgozatunkban a problémaosztályra lehetőség szerint általános megoldást keresünk, ám a platformok széleskörű áttekintése nagyon szerteágazóvá tenné a kutatást. Azért, hogy végül kézzelfogható eredményig juthassunk el, kutatásunk elején egyetlen platform mellett köteleztük el magunkat. Mivel ezt a döntést általános probléma esetén talán lehetetlen meghozni, ezért a technológia kiválasztásakor a konkrét példaalkalmazás igényeit is figyelembe vettük. Az BME műszaki informatikus hallgatói a Számítógép Laboratórium VI. tantárgy teljesítéséhez méréseket végeznek. A mérések beugróval kezdődnek, majd laborvezető segítségével mindenki elkezd egy egyéni feladatot, ezt otthon befejezi, és három napon belül beadja a hozzá elkészített jegyzőkönyvet. A folyamat irányítására és a szükséges oktatói háttérmunka megkönnyítésére egy webes alkalmazás szolgál. Ezt az alkalmazást választottuk példaalkalmazásnak. Az általános problémaosztályra kidolgozott megoldásokat az alkalmazás újraírása során próbáltuk ki a gyakorlatban. Mivel dolgozatunk témája a séma, ezért a konkrét alkalmazásról csak kevés szót ejtünk a róla a következő fejezetekben. Viszont a projekt néhány részletét érdemesnek tartottuk bemutatni, ezért a függelékben található egy részletesebb leírás a projekt menetéről.
7
A példaalkalmazás által támogatott folyamatról az 1. ábra1 mutat vázlatos képet. Az ábrán szereplő fogalmakat nem részletezzük, mert erre nincsen szükség a dolgozat további részeinek megértéséhez, de bízunk benne, hogy a feladat jellegét jól szemléltetik.
1. ábra – a példaalkalmazás által támogatott folyamat vázlatosan
A Feladatok előkészítése fázisban egy dokumentumtár funkcionalitására van szükség. A félév előkészítésekor, a hallgatók létrehozása és a feladatok hozzájuk rendelése során adat-intenzív műveleteket futtatunk. Végül a félév során a különböző szereplők többször belépnek, és kisebb mennyiségű adatot töltenek le vagy fel, egymás eredményeire várva. A harmadik rész teszi ki a működés döntő részét.
2.3. Technológia-választás Olyan technológiát keresünk, amely a kijelölt problémaosztály feladatainak megoldására általánosságban alkalmas. Az optimalitást azzal mérjük, hogy a lehető legjobban megfeleljen az 1. ábrán bemutatott három fázis által támasztott általános igényeknek. Ezek
1
Megjegyezzük, hogy ez nem egy UML szemantikát követő use-case diagram, csak egy személtető ábra
8
közül a legfontosabb a Félév menete, mert ezt a funkcióhalmazt használják a felhasználók az esetek túlnyomó többségében. Felmerült, hogy egy különálló asztali alkalmazás álljon az adminisztrátor rendelkezésére2, amely segítségével hatékonyan végezhet adatintenzív műveleteket, így ennek megvalósíthatósága is szempont lehet a döntésnél. Mivel a felhasználók zöme a legkülönbözőbb számítógépekről szeretné használni az alkalmazást, ezért a vékony kliensre alapuló, azaz webes alkalmazás technológiák között keressük a megoldást. Fontos kikötés, hogy a technológia az operációs rendszerre ne adjon megkötést.
2.4. Webes alkalmazás technológiák Az informatikában nagyon nagy szerepe van az Interneten keresztül nyújtott szolgáltatásoknak, ezek között is a web alapú szolgáltatások részesedése túlnyomó, melyben a klienssel való kapcsolat HTTP protokoll szerinti, a kliens oldalon HTML és JavaScript programok és leírók szerepelnek (továbbá XML, XSLT, CSS, DHTML, Java Applet, Flash és ActiveX vezérlők használata elterjedt). Az alkalmazások nagy része azonban szerveroldalon kerül futtatásra, a HTTP kapcsolat másik oldalán. Természetesen van lehetőségünk saját webkiszolgáló írására, amely a dinamikus működést megvalósítja, ennél azonban rugalmasabb megoldás nyújt az, ha a már meglévő – statikus tartalmat kiszolgáló – webszervert tudjuk kiegészíteni az alkalmazásunkkal. Erre univerzális megoldást biztosít a Common Gateway Interface (CGI) [5], amely használatával a bekonfigurált HTTP kéréseket a webszerver a mi modulunknak továbbítja, majd a modulunk által adott választ adja vissza HTTP válaszként a kliensnek. Egy másik igen korán elterjedt megoldás bizonyos szkript végrehajtók alkalmazása. Ezekhez a webszerverek integráltan nyújtanak támogatást. Korábban Perl, ma sokkal inkább a PHP [4] a legelterjedtebb szkript motor, de használnak még Pythont is, és ide tartozik az ASP és ASP.NET. A CGI megközelítés egy továbbfejlesztése a Java Enterprise Edition által bevezetett Servlet [6] technológia, ahol szintén az általunk megírt program szolgálja a ki a HTTP (vagy egyéb protokollú) kérést. Tekintsünk át néhány példaalkalmazásunk fényében!
elterjedt
megvalósítást
és
előnyeiket-hátrányaikat
a
2.4.1. PHP A PHP-t hamar elvetettünk, több okból is. Mielőtt a döntésünket indokolnánk, nézzük meg, hogy milyen alapvető feladatokból áll egy webes kérés kiszolgálása. Először a kérés információból (a HTTP kérés fejléce és tartalma) ki kell bányásznunk a nekünk releváns és szükséges információt. Ezek alapján el kell döntenünk, hogy mit kell tennünk, majd ezt meg is kell tennünk. Ez általában üzleti adatok módosítását és az alkalmazás állapotának megváltoztatását jelenti. Meg kell határoznunk ezután, hogy mit kell prezentálnunk válaszként a kliensnek, majd a választ elő kell állítanunk. A válasz általában egy HTML dokumentum. Egy dinamikus tartalommal rendelkező HTML oldalt nagyon kényelmesen állíthatunk elő szövegkezelő szkript motorokkal, mint a Perl vagy a PHP, ezért nagyon csábítók ezen technológiák. Észre kell vennünk azonban, hogy az üzleti adatok kezelésére egy objektum orientált programozási nyelv sokkalta alkalmasabb. Alapvető fontosságú észrevennünk a magas szintű programozási nyelvek és a szkript nyelvek közötti különbséget.
2
Erről a függelék 6.1.6 pontjában írunk részletesebben.
9
A PHP-ba épülő kiegészítő függvénykönyvtárak segítségével igen sok lehetőségünk van a leginkább szükséges adatbázis kezelésre, és további funkciók használatára is, azonban egy jól strukturált alkalmazás írására magas szintű nyelvet ildomos választanunk. A megjelenítési rétegben (a HTML kimenet előállításában) a Java Server Faces (JSF) technológia és egyéb keretrendszerek ugyanolyan kényelmes eszközt adnak, mint amit a PHP képes nyújtani. Megjegyzendő, hogy egy kisebb dinamikus weboldal kiszolgálására a PHP nagyon is alkalmas, valóban gyorsabban elkészíthető a kész termék, továbbá egy PHP kiszolgáló sokkal kisebb erőforrás-igényű, és sokkal könnyebben elérhető, mint egy Java-t futtató szerver. Amikor azonban üzleti alkalmazást fejlesztünk, fontos formális módszereket, eszközöket és megfelelő fejlesztési módszert alkalmaznunk, amelyek a helyes működésű termék előállítását nagyban segítik, ezek viszont magas szintű nyelvekre épülve elérhetők.
2.4.2. ASP.Net Az ASP.Net erősen objektum orientált, robusztus keretrendszer, melyben sok kész vezérlő és technika segíti a fejlesztést. Nagy hátránya azonban, hogy gyakorlatilag csak Windows kiszolgálón futtatható a vele készült alkalmazás. Ez viszont jelen feltételek között nem volt megengedhető, és a megvalósítási sémánkba sem kívántunk ilyen erős megkötést beépíteni.
2.4.3. Moodle keretrendszer Bár első olvasásra furcsának tűnik, de felmerült az a lehetőség is, hogy egy meglévő, hasonló alkalmazást alakítsunk át az adott problémaosztálynak megfelelően, vagy egy, a feladatot megoldó modult fejlesszünk egy általános alkalmazásrendszerbe. Rövid vizsgálódás után a Moodle-t [2] találtuk a feladathoz leginkább közeli alkalmazásnak. Ez egy nyílt forráskódú Learning Management System, mely elsősorban felsőoktatásbeli kurzusok lebonyolítására szolgáló webes portál. A számunkra hasznos funkciók, melyek készen álltak a Moodle-ben: •
További előny, hogy a felhasználói felületre kész koncepciót ad, és nagyon sok megjelenés („theme”) áll rendelkezésre, és a feliratok több nyelven, köztük magyarul is megjelenhetnek. Hátránya, hogy PHP+MySQL platformra készült, így nagyobb terhelés esetén problémák merülhetnek fel, a skálázáshoz komoly üzemeltetői szakértelemre van szükség. A Moodle üzemeltetőknek szóló fórumok [3] tanulsága szerint helyes beállítások esetén is csak több száz, legfeljebb ezres nagyságrendű felhasználó kiszolgálásához elégséges egy erősebb munkaállomás hardver. Viszont ennél a nagyságrendnél is sok kevésbé szakavatott üzemeltető beleütközött a kapcsolatok számára és a lefoglalt memória méretére vonatkozó korlátokba. Továbbá ezen a platform hibája a sűrű kapcsolódás az adatbázishoz és tipikus hiba az adatbázisok helytelen indexelése, aminek rendkívül alacsony teljesítmény az eredménye. Ezeket a jelenségeket a Schönherz Zoltán Kollégium közösségi életét támogató portáloknál is tapasztaltuk, így a Moodle használatát elsősorban az alapul szolgáló technológia miatt vetettük el. 10
A kész funkciók vizsgálata során azt állapítottuk meg, hogy egy Learning Management System olyan extra szolgáltatásokat nyújthat, melyek a kész alkalmazásunkat kedveltebbé tehetik a felhasználóknak, de az elvárt alapfunkcionalitást teljes egészében nekünk kell kifejlesztenünk. Mivel a feladatunk végül mindig egy speciálisabb munkafolyamat támogatására fog vezetni, nemigen fogunk olyan keretrendszert találni, amely ebben segít minket. Általános, elterjedt munkafolyamatokra léteznek rendszerek, de az általunk vizsgált problématípus nem az. Mindig is lesznek speciális munkafolyamatok, az ezek támogatására készült alkalmazások fejlesztésének hatékonyabbá tételére felhasználható általános megoldásokat alacsonyabb szinten kell keresni. Ha mégis kész alkalmazást szeretnénk alapul felhasználni, akkor mérlegelni kell: a kész extra szolgáltatásokért megéri-e az alkalmazást beleintegrálni egy másik rendszerbe? Az integráció mindenképp többletmunka és nehézséget jelentenek a verzióváltások. Nagyon könnyen előfordulhat, hogy a keretrendszer modellje (például a felhasználói szerepkezelés) tágabb vagy szűkebb, mint a kifejlesztendő alkalmazás. Az előbbi eset nem okoz problémát, esetleg adminisztrációs vagy számítási többletmunkát. A második esetben viszont az alkalmazás modellje nem lesz tiszta, kényszermegoldásokat kell alkalmazni. Különösen veszélyes az, hogy ez lehet, hogy csak a fejlesztés későbbi fázisaiban, vagy továbbfejlesztéskor derül ki. Tehát egy kész alkalmazásba történő integráció gyorsabbá teheti a fejlesztést, és értékes kiegészítő szolgáltatásokat adhat, viszont kockázati tényezőket hoz be a fejlesztés és továbbfejlesztés során, illetve megköti a platformot. Ennek fényében ezt a lehetőséget elvetettük.
2.5. Java A Java egy széles körben elterjedt, erősen objektum-orientált nyelv, így kiváló talajt biztosít egy jól tervezett alkalmazás implementálásához. A Java szinte minden operációs rendszeren elérhető, így nem köti meg kezünket a szerverrel kapcsolatban sem. A Java Servlet technológia az általános CGI megközelítés mintájára ad lehetőséget szerveroldali alkalmazások készítésére, ezen túl kiterjedt támogatást nyújt a különféle webes technológiák alkalmazásához. Az üzleti logikai részt a web kérdéseitől függetlenül készíthetjük el, majd sokféle technológia közül választhatunk a felhasználói felület megalkotásakor. A fejlesztés során felmerülő gyakori problémákra biztosít kiforrott megoldásokat a Java Enterprise Edition platform.
2.6. Java Enterprise Edition A Java Enterprise Edition tulajdonképpen Java platform alapú specifikációk gyűjteménye. Ezek az ún. Java Specification Requestek (JSR) [10]. Minden specifikáció egy, a nagyvállalati környezetben történő alkalmazásfejlesztés és üzemeltetés során felmerülő gyakori problémakört jár körül. Ilyen lehet például a tranzakció-kezelés [JSR907], az üzenetek kezelése [JSR914] vagy az alkalmazás menedzsment [JSR77]. Ezek a specifikációk olyan irányelveket rögzítenek az adott problémakörökben, melyek betartásával különböző gyártók által Java platformon fejlesztett megvalósítások könnyedén kombinálhatók lesznek egymással azon túl, hogy robusztus megoldásokat nyújtanak az adott problémákra. Maga a
11
Java EE is tulajdonképpen egy JSR specifikáció, a legújabb 5.0-ás verzióját a [JSR244] definiálja.
2.6.1. A Java EE alkalmazásszerver Ha a Java EE specifikációjában szereplő komponensek implementációit a specifikációnak megfelelően összeintegráljuk, akkor egy Java EE kompatibilis alkalmazásszervert (Application Server, AS) kapunk. Ilyen alkalmazásszerverekre lehet telepíteni az általunk fejlesztett alkalmazásokat, amelyek főként beanekből állnak. Egy bean nem más, mint szolgáltatások összessége, amelyeket más beanek vagy programok igénybe tudnak venni. Az adott alkalmazáson belül tehát a beanek szolgáltatásokat nyújtanak egymásnak, illetve néhány szolgáltatásukat az alkalmazáson kívülről is elérhetővé teszik. Ezeket a szolgáltatásokat kliensként vehetik igénybe más programok, amelyek lehetnek egyszerű Java alkalmazások, vagy más, alkalmazásszerveren futó Java EE alkalmazások beanjei. Ez utóbbi esetben a beanek futhatnak egyazon alkalmazásszerveren, vagy akár egy másik számítógépen futó alkalmazásszerveren is. Ilyenkor természetesen hálózaton keresztül kapcsolódnak az alkalmazásszerverhez, éppen úgy, ahogy az egyszerű Java alkalmazások is teszik, amennyiben egy alkalmazásszerveren futó bean szolgáltatását akarják igénybe venni.
2.6.2. A Java EE különböző verzióiról A jelenleg legjobban elterjedt verzió a J2EE 1.4-es platform [JSR151], amelynek 16 kompatibilis applikáció szerver implementációja létezik a különböző gyártók kínálatában [11] [12], melyek között ingyenesen felhasználható és kereskedelmi termékek is előfordulnak. 2006. május 11-én jelentették be a Java EE 5.0 platform véglegesített specifikációját [JSR244], illetve ezzel egyszerre a Sun referencia implementációját, a Sun Java System Application Server Platform Edition 9-et. Mi ezt a nagyon korszerű technológiát választottuk a sémánk alaptechnológiájának annak ellenére, hogy a megjelenés óta eltelt kevés idő alatt ez a verzió még nem terjedt el annyira, mint elődje. Ugyanakkor már több neves gyártó dolgozik a saját implementációja elkészítésén, mint például az SAP [11]. Másrészt az új platform elterjedésére garancia lehet még a régi verziókkal való kompatibilitás, valamint az a rengeteg újítás, amivel éppen a korábbi verziók hiányosságait igyekszik felszámolni. Ha egy mondatban kellene összefoglalni a különbségeket a Java EE 5.0 és a J2EE 1.4 között, akkor azt mondhatnánk, hogy az 5.0 verzióban a fejlesztés sokkal gyorsabbá, átláthatóbbá, hatékonyabbá vált. Ahogyan ezt a Java EE 5 mottója is tükrözi: "Do more with less work."
12
3. A megvalósítási séma E fejezetben adunk részletes leírást a kialakult megvalósítási sémáról. Bizonyos részeken a már bevált technológiákat ismertetjük, míg kevésbé kiforrott területekről egy széleskörű áttekintés után kialakult saját koncepciónkat ismertetjük. Az üzleti alkalmazások fejlesztése során alapvetően kétféle architektúra szerint építhetjük alkalmazásunkat. A kétrétegű architektúrában az üzleti adatokat kezelő réteg és a vezérléssel és megjelenítéssel foglalkozó réteg különül el. Ennek komoly üzleti alkalmazásoknál való használatának hátrányairól már a bevezetésben is szóltunk a PHP kapcsán, és jelen fejezetben is még visszatérünk rá. A háromrétegű architektúrában az üzleti réteg mellett elkülönítjük a vezérlés és a megjelenítés rétegét, ez könnyebben kezelhető és jobban átlátható forráskódhoz vezet [1]. Dolgozatunkban négy rétegről beszélünk. Az üzleti rétegben külön tárgyaljuk az üzleti adatok modellezésével és tárolásával kapcsolatos kérdéseket, valamint az üzleti adatokon végzett műveletek és adatlekérdezések (üzleti funkciók) rétegét.
Megjelenítés Vezérlés és Megjelenítés
Adat
Vezérlés
Adat
Megjelenítés Vezérlés
Üzleti Logika
Adatmodell 2. ábra – A kialakult négy réteg
A célkitűzés során elköteleztük magunkat a Java EE technológia alkalmazása mellett. Fontos tudnunk, hogy a Java EE-hez nem kapcsolódik egy konkrét fejlesztőeszköz vagy konkrét architektúra. A platform nagyban segíti az üzleti modell és az üzleti funkciók rétegének fejlesztését, viszonylag behatárolja ezek struktúráját. Nem ad viszont semmiféle megkötést a vezérlés felépítésével kapcsolatban. A megjelenítésre a Java Server Pages egy széles körben használható eszköz, illetve a Java 5 EE-ben a Java Server Faces nyújt bizonyos újításokat. A fejezetben áttekintjük a Java EE által nyújtott lehetőségeket az adat és az üzleti logika rétegeiben. Széleskörű áttekintést adunk a vezérlési és megjelenítési rétegben használható, harmadik fél által fejlesztett keretrendszerekről. Az itt szerzett tapasztalatok fényében bemutatjuk a kialakult saját koncepciónkat és egy általunk kifejlesztett keretrendszert. A fejezet végén a megjelenítési technológiák kérdéseit külön vizsgáljuk. Mielőtt a négy réteg bemutatnánk, áttekintést adunk a Java EE platformról.
13
3.1. A Java EE platform részletes bemutatása 3.1.1. A beanekről Mint említettük korábban, a bean szolgáltatásokat definiál és azokat mások számára elérhetővé teszi. Természetesen egy alkalmazás által nyújtott szolgáltatások típusukat tekintve is sokfélék lehetnek. Ennek megfelelően a Java EE platformon használatos beaneknek is több típusa létezik [7] [8]: •
Az entitás beanek (Entity Beans) az alkalmazásban használt adatok modellezésével és tárolásával foglalkoznak. Segítségükkel az üzleti alkalmazás adatmodelljét entitásokból építhetjük fel, ahol minden entitástípust az entitás által birtokolt attribútumok definiálnak. Ezen belül pedig egy entitáspéldányt az attribútumokhoz rendelt értékek határoznak meg, egyértelműen. Az entitástípusok között kapcsolatokat, relációkat definiálhatunk, amelyek jellegüket tekintve szintén többfélék lehetnek. Ezeket az adatokat és kapcsolatokat az adatmodellezésben jól ismert módszerekkel ábrázolhatjuk entitás-relációs vagy osztálydiagramon. A modell megalkotásán túl általános feladat még az entitások állapotának kezelése, úgymint az attribútumok és a relációk megadása/megváltoztatása, valamint a végrehajtott változtatások maradandó formában való tárolásának megoldása. Ez utóbbira a Java EE platformon például a napjainkban egyre jobban elterjedő ún. Object-Relational Mapping (ORM) technikák nyújthatnak integrált segítséget. Ennek részleteiről az adatmodell réteg bemutatásánál lesz szó.
•
A szolgáltatás beanek (Session Beans) a procedurális folyamatokat írják le. Ezek tárolják mindazokat az algoritmusokat, amelyek az üzleti problémakör egyes problémáinak vagy részproblémáinak megoldási menetét definiálják. A szolgáltatás beanek szolgáltatásainak igénybevételével ezen folyamatok végrehajtását kezdeményezhetjük. Az itt általánosan felmerülő feladatok közé tartozik a különböző eljárások végrehajtásához való jogosultságok kezelése, vagy például a végrehajtott folyamatok atomiságát biztosító tranzakció-kezelés megoldása. A Java EE környezetben a szolgáltatás beanek fejlesztése során mindezekre egyszerűen kezelhető mégis robusztus megoldásokat vehetünk igénybe. A szolgáltatás bean rendelkezhet olyan állapot információkkal, amelyek egy adott kliens felől érkező két hívás között megőrzik állapotukat. Ilyenkor állapottal bíró szolgáltatás beanről (Stateful Session Bean) beszélünk, míg ellenkező esetben állapot nélküli szolgáltatás beannel (Stateless Session Bean) van dolgunk.
•
Ennél speciálisabb funkciója van az üzenetvezérelt beaneknek (Message Driven Beans). Ezek aszinkron üzenetvezérelt kommunikációt tesznek lehetővé akár az alkalmazás egyes részei között, akár kooperáló alkalmazások között.
A fent említett három bean típus nagyrészt lefedi az üzleti problémák megoldásához szükséges területeket. Ezek együttesen alkotják az Enterprise JavaBeans specifikációját, melynek legújabb, 3.0-ás verzióját a [JSR220] specifikálja. Ezen kívül léteznek még más, speciális funkciót megvalósító bean típusok is. Ezek elsősorban nem az üzleti problémák megoldását hivatottak támogatni, hanem az alkalmazással kapcsolatos egyéb feladatokat. Ilyenek például a menedzsment beanek (Management Beans), amelyek az egyes szolgáltatások menedzselésével kapcsolatos funkciókat láthatnak el, például a szolgáltatások betöltése, elindítása, leállítása.
14
A beanek használatának értékelése Vizsgáljuk meg ezek után, hogy a fent megismertetett alkalmazásfejlesztési koncepciók milyen előnyöket nyújtanak a fejlesztés során. •
Először is fontos kiemelni, hogy a beanek használata a problémák dekompozíciójára késztet, ami lehetővé teszi, hogy a bonyolult problémákat kis, könnyebben átlátható és kezelhető részproblémákra bontsuk. Éppen ezért a dekompozíció alapeleme a modern szoftverfejlesztési gyakorlatoknak.
•
A beanek típusainak szétválasztása pedig arra készteti a fejlesztőt, hogy különválassza az alkalmazás kódjának különböző jellegű részeit. Ez is nagyban növeli a kezelhetőséget és az átláthatóságot, valamint szinte elkerülhetetlen nagy rendszerek fejlesztése esetén.
•
Szintén fontos pozitívum, hogy valamilyen alkalmazásfejlesztési sémához rögzíti a fejlesztőt, amely bár elég általános ahhoz, hogy ne jelentsen korlátozást a problémák megvalósíthatóságra nézve, de mégis egy könnyen átlátható struktúrát ad a rendszernek. Ha ez még párosul is az adott szoftverfejlesztési gyakorlat elterjedtségével, akkor lehetővé teszi, hogy egy a platformot ismerő programozó hamarabb el tudjon igazodni egy számára ismeretlen kódban, mint általános esetben. Ez a Java EE platformra – a szigorú előírások miatt – kiemelten igaz.
3.1.2. Az alkalmazásszerverről részletesebben Mint korábban megemlítettük, az alkalmazásunk beanjeit egy arra alkalmas alkalmazásszerver futtatja. Vizsgáljuk meg most egy kicsit közelebbről az alkalmazásszerver alapú koncepciót. Ez valójában a kliens-szerver architektúra alapján történő fejlesztést hivatott megkönnyíteni. Ugyanis annak ellenére, hogy a kliens-szerver modell általánosan jól használható, egy szerver alkalmazás kifejlesztése tradicionális módszerekkel sokkal több munkát igényel, mint amennyit a megoldandó üzleti feladat megkívánna. Ez legtöbbször abból adódik, hogy olyan problémákat kell megoldani a szerver alkalmazás fejlesztése során, mint például a konkurens működés, tranzakció-kezelés vagy éppen az elosztott működés. Ezen a problémakörök önmagukban is elég összetettek és sok csapdát rejtenek, így aztán az ezekbe fektetett munka sokszorosa lehet a valódi probléma megoldására fordított költségeknek. Ezen próbál segíteni az alkalmazásszerver alapú koncepció azáltal, hogy az általánosan előforduló problémákra robusztus, integrált megoldásokat biztosít, így a fejlesztőknek csak a valódi probléma megoldására kell közvetlenül koncentrálniuk. Az említett megoldások hatékony megvalósításával pedig az alkalmazás teljesítménye is növelhető. A kompatibilitás megőrzése Fontos azonban kiemelni egy problémát, ami éppen az integrált megoldások alkalmazásából adódik. Ez az adott megvalósítástól való függés. Ha ezek a megoldások nem képesek más rendszerekkel együttműködni, vagy adott esetben velük kompatibilisek maradni, akkor ez nagy visszatartó tényező lehet. Ezt a problémát igyekszik kiküszöbölni a Java EE platform a specifikációk rögzítésével. Ennek köszönhetően a Java EE platformra fejlesztett alkalmazások nagyrészt függetlenek tudnak maradni a konkrét alkalmazásszerverimplementációtól. Ez természetesen csak bizonyos korlátok mellett teljesülhet, hiszen a specifikációk nem kezelik, nem kezelhetik annyira szigorúan az egyes implementációs előírásokat, különben a gyártóknak nem maradna mozgástere a saját implementációjuk elkészítése során. Mégis lehetőség van a specifikációkat követve olyan Java EE alapú alkalmazást készíteni, ami könnyedén futtatható különböző gyártók kompatibilis implementációin. 15
Itt jegyezzük meg, hogy önmagában a lehetőség a kompatibilitásra még nem lenne elég, ha a technológia nem lenne támogatott, és nem léteznének ténylegesen különböző implementációk. A Java EE esetében elmondhatjuk, hogy nem ez a helyzet. A platform eddigi sikereinek köszönhetően olyan gyártók sorakoztak fel a specifikációk mellett saját implementációikkal, mint például az IBM [13], az Oracle [14], az SAP [15] és Java platform megalkotója, a Sun [16]. Fontos szempont továbbá, hogy a platform folyamatosan fejlődik megoldásokat kínálva a korábbi verziók hiányosságaira, amelyekre a tényleges fejlesztések során derül fény igazán. Ennek ellenére a specifikációkban mindig nagy hangsúlyt fektetnek a korábbi verziókkal való kompatibilitás megőrzésére, illetve a kompatibilitás fenntartásának lehetőségére. Ez nagy pozitívum a platformon már kifejlesztett alkalmazások élettartamának megítélésében.
3.1.3. A Java EE 5.0 újításairól Ebben a részben azt tekintjük át, hogy mitől vált tényleg hatékonyabbá a Java EE 5.0 platformon való fejlesztés a korábbi verziókhoz képest. A továbbiakban elsősorban a J2EE 1.4-ben használt EJB 2.1 [JSR153] [7] és a Java EE 5.0-ban bevezetett EJB 3.0 [JSR220] [8] közötti különbségekre alapozunk, ezekből indulunk ki, valamint megemlítjük az általánosan alkalmazható újításokat is. A meta-információkról Az applikáció-szerver alapú koncepciót követve a tradicionális fejlesztéshez képest megjelenik egy új fejlesztési lépés: a komponensek működésének összehangolása, integrálása egymással, más alkalmazások komponenseivel illetve a platform által nyújtott szolgáltatásokkal. – Ez a fejlesztési lépés az angol nyelvhasználatban az application integration nevet viseli. – Ezt a műveletet célszerű elválasztani a forráskód írásától, hiszen akkor tudjuk kiaknázni a modularitásból és az újrafelhasználhatóságból adódó előnyöket. Éppen ezért már a korábbi verziókban is megszokott gyakorlattá vált – főleg nagy projektekben – hogy az alkalmazás összeállítását erre a feladatra dedikált emberek végzik, akik lehet, hogy részt sem vesznek az egyes komponensek fejlesztésében. Sőt – szintén főleg nagy projektek esetén – gyakran előfordul, hogy más által fejlesztett és saját fejlesztésű komponensekből állítják össze az alkalmazást. Célszerűtlen lenne emiatt, ha az együttműködést leíró információkat a forráskódban elhelyezett utasítások formájában kellene tárolni, hiszen így a komponens nem maradhatna független a felhasználási környezetétől. Ez éppen a komponensek újrafelhasználhatóságát korlátozná jelentős mértékben. Ezért – a Java EE 5.0 előtti verziókban – különálló leíróállományokat használtak ezeknek a meta-információknak a leírására. Ezek az állományok az esetek nagy többségében XML formátumúak. Ez jó megoldás abból a szempontból, hogy az egyes komponensekhez a felhasználásuk különböző helyein más-más leíróállományokat készíthetünk. Felmerült azonban egy olyan probléma ezzel kapcsolatban, amely mégis sok kényelmetlenséget szült a fejlesztések során. Nyilvánvaló, hogy mivel a leíróállományokban a komponensekre, a Java nyelven megírt osztályokra, azok metódusaira, attribútumaira hivatkozunk, általában a nevüket használva. Így azonban mégsem válhat függetlenné a forráskódtól a leíró állomány – ami egyébként teljesen természetes is – viszont kényelmetlen állandóan a két állomány között váltogatva készíteni a leíró állományt, mindemellett sok plusz munkát is jelent. Az integráción dolgozó fejlesztők jobban szerették volna az együttműködési információkat annak a közelében elhelyezni, amire azok ténylegesen is vonatkoznak. Ezért hamar kialakultak olyan eszközök, amelyek a forráskódba ágyazott megjegyzésekben 16
speciálisan elhelyezett információk alapján legenerálták a szükséges XML leíróállományokat. Az egyik legelterjedtebb ilyen eszköz lett az XDoclet. Ezzel a megoldással viszont az volt a probléma, hogy az esetlegesen elkövetett jelölésbeli tévesztések miatt így is szükség volt a legenerált XML állományok végignézésére, ellenőrzésére. Ezen kívül a legenerált állományokat kézzel módosítani sem volt célszerű, – hiszen a módosítások a következő generálás során megsemmisültek volna – pedig erre a leíróállományok véglegesítésekor gyakran szükség lett volna. Erre a Java EE 5 a következő megoldást nyújtja: a leíró meta-információk nagy része már a Java SE 5.0-ban [JSR176] annotációk (Annotations) által kerül tárolásra. Ez nem más, mint egy nyelvi támogatás meta-adatok tárolására a Java nyelvben. Ennek több előnye is van. Egyrészt az adatok azon dolgok közelében kerülnek tárolásra, amire vonatkoznak, ez segíti az átláthatóságot azáltal, hogy a fejlesztő látja azt, amire a meta-információk vonatkoznak. Másrészt mivel nyelvi elem, ezért fordítási időben szintaktikai ellenőrzésen megy át, sőt számos integrált grafikus fejlesztőkörnyezet (IDE) biztosít szerkesztés közbeni szintaxisellenőrzést is. Az annotációk használata nem csak az EJB 3.0 specifikációjának megfelelő beanek fejlesztésében van jelen, hanem annotációkkal definiálhatjuk például a tranzakció-kezelés paramétereit, a biztonsági beállításokat és jogosultságokat, vagy a szolgáltatások névtérben (JNDI) történő megkeresését, regisztrálását. Természetesen ezekben az esetekben is megmaradt az XML leíró állományok használatának, valamint a két módszer kombinálásának lehetősége is. Generikus típusok Jelentős előrelépések történtek továbbá afelé, hogy a sok helyen előforduló, hasonló kódrészletek által elvégzett feladatokat általánosan megvalósítsák, amennyiben erre lehetőség van. Jó példa erre a beanek életciklusának kezelése, amelyhez az EJB 2.1-ben még interfészeket kellett definiálni, illetve azok metódusait implementálni, amelyek kódja mindig azonos sémára épült. Az ilyen funkciók a Java SE 5.0 másik újításának, a generikus típusoknak köszönhetően általánosan implementálva lettek, megszabadítva a fejlesztőt ezek elkészítésének terhétől. Meg kell jegyeznünk, hogy a generikus programozás csak a Java SE korábbi verzióihoz képes újítás, más programozási nyelvekben, mint például a C++, már korábban is létezett. A Java SE 5 implementációja ezeknek a tapasztalataiból merített, és sikerült egy a technológia általános gyermekbetegségeitől mentes megvalósítást kidolgoznia. Fontos szempont az is, hogy az ilyen általános megoldásoknak általában sokkal robusztusabbnak kell lenniük, több szolgáltatást kell tudniuk nyújtani, mint az egyénileg készített implementációk. Ehhez kapcsolódóan jelentős előrelépés figyelhető meg a leggyakrabban alkalmazott stratégiák alapértelmezésként való felhasználásában, valamint a korábbi verziókban szereplő nehezen vagy kényelmetlenül használható megoldások logikusabbá tételében. A mindezeknek köszönhető egyszerűsödést jól példázza az, hogy míg az EJB 2.1-es verziójában 4–6 fájl volt szükséges egy entitás bean leírásához (1 entitás osztály, 2–4 interfész és egy leíró XML állomány), addig az EJB 3.0 specifikációit követve mindez egyetlen Java osztályban megtehető az annotációk használatával. A régi rendszerekkel való kompatibilitás megőrzése érdekében pedig megmaradt az XML leíró állományok használatának lehetősége, valamint a korábbi entitás-életciklust vezérlő interfészek emulálása vagy használata is módunkban áll. Ennek köszönhetően a 2.1-es EJB specifikációnak megfelelő beanek gond nélkül igénybe tudják venni a 3.0-ás specifikáció alapján készített beanek szolgáltatásait, és fordítva.
17
A perzisztencia kezelése Az EJB 3.0 specifikációjában talán a legnagyobb áttörés az entitások perzisztenciájának kezelésében történt. Az EJB 2.1-ben – elsősorban más kiforrott módszerek hiányában – a perzisztencia kezelése teljesen a felhasználóra volt bízva [7]. Erre a specifikáció csak keretet biztosított. A legelterjedtebb gyakorlatok szerint ez úgy történt, hogy az entitás betöltésekor és eltárolásakor lefutó metódusok SQL kéréseket futtattak egy adatbázisban JDBC-n keresztül, így tárolták el az entitás objektum attribútumainak értékeit az adatbázisban. Ez a módszer is rendkívül sematikus volt, ennek megfelelően hamarosan megjelentek olyan eszközök, amelyek grafikus varázslókon keresztül legenerálták ezeket a kódokat. A kódgenerálással kapcsolatos aggályainkat pedig már a meta-információkról szóló részben kifejtettük. A Java EE 5 megoldása erre a problémára az ún. Container Managed Persistence (CMP) bevezetése [8], amellyel a következőképpen dolgozhatunk: •
Annotációkkal definiáljuk, hogy az entitás beanek egyes attribútumait milyen stratégia szerint szeretnénk tárolni, például milyen adattípusként akarjuk kezelni a bennük tárolt értékeket. Ezek a beállítások természetesen amennyire lehet, automatikusak, tehát például az adattípusokat a rendszer többnyire a forráskódból deríti ki, a fejlesztőnek csak a kétes esetekben – például dátumok kezelésénél – kell specifikálnia a beállításokat.
•
Az applikáció szerveren ezután egy perzisztencia- vagy entitásmenedzser (persistence manager) az annotációkban definiált beállításoknak megfelelően elvégzi az entitás objektumok adatainak eltárolását illetve visszatöltését az alkalmazásszerver és az adatbázis között. Maga az entitásmenedzser egy meghatározott programozói interfészt, konkrétan az EJB 3.0 Persistence API-t követi [JSR220], ennek köszönhetően az egyes entitásmenedzser-megvalósítások az alkalmazásszerveren cserélhetők. Sőt, az alkalmazásszerveren futó különböző alkalmazások egy időben más-más entitásmenedzsert is használhatnak. Léteznek olyan entitásmenedzser implementációk is, amelyek a különböző adatbázis kezelő rendszereket is egységesen tudnak kezelni, ráadásul néhány esetben úgy, hogy még azok speciális lehetőségeit is ki tudják aknázni. Ez biztosítja alkalmazásaink számára a függetlenséget egy-egy adott adatbázis kezelő rendszer használatától, illetve annak gyártójától is.
•
Az entitások elérésére, vezérlésére az EJB 3.0 Persistence API-ban definiált EntityManager interfészen keresztül van lehetőségünk - többek között. Ez kínálja fel az
entitás-életciklus vezérlési metódusait is, mint például az entitás tartalmának mentése, betöltése, frissítése, vagy az entitás törlése. Emellett természetesen megmaradt az egyéni perzisztencia-kezelés lehetősége is, ezt nevezi az EJB 3.0 specifikációja Bean Managed Persistence-nek (BMP). Szemléletváltozás a komponensek függésében Végül, de nem utolsó sorban ejtsünk szót a technológiai szempontból talán legkomolyabb szemléletváltásról, fejlődésről [8]. Ehhez azt kell tudnunk, hogy Java EE 5.0 előtt a beaneket alkotó osztályoknak és interfészeknek a Java EE platform beépített osztályaiból vagy interfészeiből kellett származniuk. Erre akkor technológiai okok miatt volt szükség. Ez az elkészített beaneket a J2EE platformhoz kötötte, vagyis a forráskódjuk közvetve nem volt újrahasznosítható a J2EE alkalmazások körén kívül, például egy Java SE kliensalkalmazásban, hiszen az ősosztályaik és interfészei itt nem voltak elérhetőek. Ez egyértelmű hiányosság, hiszen a kliens programban is szükségünk lehet például egy lekérdezett entitás attribútumait tároló osztályra, itt azonban az entitás bean már nem lesz 18
használható, új osztályt kell létrehoznunk erre a célra. Az említett szituációban ez egy bevált gyakorlat volt a J2EE 1.4-es és azt megelőző verzióiban. Ezeket az objektumokat adathozzáférési objektumoknak (Data Access Object, DAO) hívták [7]. A fenti elveket követő komponenseket az angol terminológiában "corse grained" komponenseknek nevezik, ahol a név a komponensek és a platform közötti szoros függőségre utal. Ezzel szemben a Java EE 5.0 már az ún. "fine grained" komponensek filozófiáját követi, ahol tehát a komponensek csak lazán kötődnek a platformhoz, és nem Java EE környezetben is felhasználhatók [8]. Ezt úgy éri el, hogy nem követeli meg a bean osztályoktól azt, hogy bármilyen beépített osztályból vagy interfészekből származzanak. Ennek megfelelően tehát a bean osztályok egyszerű Java osztályok, vagy ahogyan nevezni szokták: POJO-k (Plain Old Java Object). Az osztályokban a bean típusát és az ehhez szükséges további információkat is annotációkban adhatjuk meg. Ezt Java technológiák idő közben megvalósult fejlődése tette lehetővé, melynek köszönhetően a lefordított Java osztályok működése lépésenként lejátszható. Ezáltal feleslegessé váltak a korábban ismertetett DAO-khoz hasonló megoldások, hiszen például az entitások esetében az entitás osztály egy példánya könnyedén hordozhatja az entitás információit a Java SE kliensalkalmazás felé.
3.2. Első réteg: az adatmodell Az adatok perzisztens módon való tárolásának feladata szinte minden alkalmazás fejlesztése során jelen van, hol kisebb, hol nagyobb jelentőséggel. Amennyiben ezek az adatok nagy mennyiségűek, strukturáltak és az alkalmazás szempontjából meghatározóak, akkor célszerű lehet az adattároláshoz erre a célra kifejlesztett rendszereket segítségül hívni ahelyett, hogy magunk implementálnánk ezeket a funkciókat. Amennyiben az adatokat egy vagy több alkalmazás példányai egyszerre, megosztottan használhatják, valamint szükség van az adatok között feltételek alapján történő keresések végrehajtására, akkor erre a legjobb megoldás mindenképpen egy adatbázis kezelő rendszer alkalmazása lehet. Ezek közül is elsősorban kiforrottságuk, piaci elterjedtségük és támogatottságuk miatt a relációs adatbázis kezelő rendszerek – Relational Database Management System, RDBMS – jöhetnek szóba. Az Oracle Database termékek mellett technológiai vezető pozíciójuk, valamint az Egyetemen hozzáférhető támogatottsága miatt döntöttünk – valamint részben azért is, mert a Számítógéplabor VI. tárgy keretein belül is ennek a használatával ismerkednek meg a hallgatók. Az Oracle adatbázis kezelő termékei az SQL szabványaival kompatibilisek, így például Java platformon a megfelelő Java Database Connectivity (JDBC) driver segítségével végrehajthatunk SQL utasításokat az adatbázisban. Ennek a kliens oldali megoldásnak a legnagyobb hátránya éppen abban rejlik, hogy az adatok kinyerésére vagy módosítására szolgáló utasítást a kliens oldalon rakja össze a program, sokszor a felhasználói felületen meghatározott feltételek alapján. Ezeken a hátrányosságokon az általános relációs adatbázis kezelő driverek – mint például a JDBC – valamelyest képesek segíteni, például előre fordított SQL utasítások és paraméter behelyettesítés használatával, de a hatékony fejlesztés még így is nagyfokú kódolási fegyelmet kívánna meg. Ezen kényelmetlenségek miatt már a technológia választás elején elhatároztuk, hogy ezt a szemléletet biztosan nem fogjuk követni.
3.2.1. Tárolt eljárások Nem csak a fent említett problémákra nyújtanak megoldást a tárolt eljárások, hanem teljesítmény szempontjából is komoly előrelépést hoznak a kliens oldali feldolgozáshoz 19
képest. A mögötte álló filozófia abból áll, hogy az adatbázis kezelők SQL nyelvi támogatását – ami egyértelműen deklaratív – kiegészítik procedurális végrehajtási lehetőségekkel. Ezeket általában az adatbázis kezelő rendszer saját – gyártóspecifikus – procedurális nyelvén vehetjük, amelybe SQL utasításokat ágyazhatunk. Az Oracle Database termékekben ez a nyelv a PL/SQL nevet viseli, ahol a PL a Procedural Language rövidítése, ez is utal a nyelv procedurális jellegére. A tárolt eljárás, vagy angol nevén stored procedure (SP) tulajdonképpen nem más, mint SQL valamint procedurális vezérlési és adatfeldolgozási utasítások szekvenciája, amit egy paraméteres eljárásba szervezzük, és az adatbázis szerveren tárolódik értelmezett, lefordított formában. Ezt a névvel ellátott eljárást aztán kliens oldalról – akár SQL utasításból – meg lehet hívni, át lehet adni neki a megfelelő paramétereket, és a rendszer végrehajtja az eljárásban tárolt utasításokat. Két dolgot fontos itt kiemelni, amik a jelentős teljesítménynövekedést biztosítják: 1. Egyrészt azt, hogy futásidőben a parancs értelmezése a korábban bemutatott kliens oldali szemlélethez képest itt csak töredéknyi erőforrást igényel, hiszen csak az eljárás nevét és paramétereit kell azonosítani szemben az SQL utasítások teljesítményigényes fordításával. Ebben az esetben ez a fordítás csak egyszer, a tárolt eljárás elkészítésekor történik meg, nem pedig futási időben minden híváskor. 2. Másrészt, ha a tárolt eljárás bonyolultabb műveleteket végez, – például adatlekérdezések alapján adatmódosító parancsokat hajt végre, – akkor a helyben történő végrehajtásnak köszönhetően az adatok nem kerülnek átvitelre a hálózaton, szemben a kliens oldali esettel. Ez a hálózat áteresztő képessége és késleltetése, valamint a hálózati forgalom járulékos adminisztrációs feladatai miatt jelentős teljesítménynövekedést is okozhat a kliens oldali feldolgozáshoz képest. A fent felsorolt pozitívumai miatt felmerült, hogy tárolt eljárásokat vezessünk be a sémába. Ezt a megfontolást erősítette bennünk az is, hogy így az üzleti logikát függetleníteni tudtuk volna az alkalmazás megvalósításától azáltal, hogy az üzleti logika funkcióit tárolt eljárások segítségével a szerver oldalon valósítjuk meg. Sőt még a különböző technológiák (pl.: PHP, JSP, Java) vegyítésére is lehetőséget nyitott ez a megoldás. Mindemellett volt már gyakorlatunk az Oracle adatbázis kezelő rendszerek használatában és adminisztrációjában, valamint a PL/SQL nyelv programozásában.
3.2.2. Object-Relational Mapping A szóba jöhető technológiák után kutatva merült fel egy merőben új technológia, az ún. Object-Relational Mapping (ORM) használatának lehetősége is, először akkor, amikor már egyre inkább a Java alapú technológiák felé fordultunk. Maga a technológia egy konkrét, Java alapú implementáción, a Hibernate-en keresztül került a figyelmünkbe. ORM technológia használata azt jelenti, hogy az alkalmazásunkban használt perzisztens objektumok osztályhierarchiáját egy relációs sémához kötjük. Ezzel tulajdonképpen egy kétirányú transzformációt definiálunk az adatmodell objektum-orientált és relációs ábrázolása között. A transzformáció definiálására azért van szükség, mert önmagában véve az objektum-orientált és a relációs adatábrázolási modellek közötti átjárás közel sem egyértelmű. Egyrészt különböző stratégiák állhatnak rendelkezésünkre speciális problémakörök kezelésére (ilyen például az öröklés), másrészt a két rendszer konkrét implementációjától függő eltéréseket is kezelni kell – például az adattípusok összeegyeztetését. Ezekre természetesen a rendszerbe beépített sémák állnak rendelkezésünkre, nekünk csak ki kell választanunk a konkrét esetekhez a legjobban illeszkedőt. Miután a transzformációt sikeresen definiáltuk a rendszer ezen információk alapján már automatikusan végzi el a perzisztencia kezelését, amibe legszűkebb körben beletartozik az objektum állapotának mentést (perzisztálás) és annak az 20
adatbázisból történő betöltése. Természetesen az ORM rendszerek ezen kívül még számos funkcióval rendelkezhetnek. Ilyen például a az eltárolt objektumok közötti, feltételrendszer alapú keresések támogatása. Az objektum-relációs transzformációról, elméletben Konkrét objektum-orientált nyelv vagy a relációs adatbázis kezelő rendszer rögzítése nélkül elmondhatjuk az alábbiakat: Egy osztály meghatároz egy relációt az általa birtokolt attribútumok között. Az osztály attribútumainak – konkrét nyelvtől függetlenül – tekinthetjük az osztályban definiált mezőket, de ettől összetettebb megközelítést is alkalmazhatunk, például a Java platformon elterjedt mező (field) és a hozzá tartozó getter/setter metódusok szemléletet követve. Megjegyezzük, hogy bizonyos objektum-orientált nyelvekben az attribútum fogalmára beépített támogatás áll rendelkezésre, ilyen például az Object Pascal nyelvben a property, vagy a C# nyelvben jelen lévő speciális, get/set primitívekkel rendelkező adatmező. A Java nyelvben elterjedt szemlélet ehhez képest nem nyelvi támogatáson, hanem konvención alapul. Létezhetnek olyan adatok is egy osztályban, amelyeket nem szükséges perzisztensen tárolni, ezeket tranziens attribútumoknak nevezzük. Természetesen elképzelhetők olyan osztályok is, amelyeknek minden adata tranziens, ezekkel egyszerűen nem kell foglalkoznunk a perzisztencia kezelés során. Miután meghatároztuk, hogy az egyes osztályoknak mik a perzisztens attribútumai, elkészíthetjük az osztályhierarchia egy entitás relációs (ER) diagramját. Itt már megjelennek az osztályoknak az egymáshoz való kapcsolatai, amelyek lehetnek két vagy több résztvevős, egy vagy kétirányú kapcsolatok. Ezeken belül is csoportosíthatjuk a kapcsolatokat a kapcsolódó példányok mennyiségi viszonyai alapján, két résztvevős esetben például beszélhetünk többek között egy-egyes (one-to-one), egy-többes (one-to-many) vagy többtöbbes (many-to-many) kapcsolatokról. Az ER diagram relációs sémára történő transzformálására pedig már léteznek kiforrott elméleti módszerek, amelyek a gyakorlatban, adott implementációs környezetben is általánosan felhasználhatók [17]. Elmondható, hogy minden entitás típushoz fog tartozni egy reláció – ami egy relációs adatbázisban egy táblának felel meg. Ebből következik az a kritérium, hogy minden osztályban ki kell jelölni egy azonosítót (identifier, id), amely az adott osztályban minden példányra egyedi. Ez az id az osztályhoz tartozó relációban az elsődleges kulcs szerepét fogja betölteni. Ennek megfelelően az id lehet egyetlen attribútum (ilyenkor egyszerű azonosítóról beszélhetünk) vagy több attribútum összessége (összetett azonosító). Az id attribútumai lehetnek az osztály "természetes" attribútumai, – ahol a természetes kifejezés alatt azt értjük, hogy az attribútum az osztályban a transzformáció megalkotása előtt is létezett, – vagy felvehetünk e célra új attribútumot az osztályba, ilyenkor logikai id-ről beszélhetünk. Az legelterjedtebb gyakorlat erre vonatkozóan az, hogy minden osztályban logikai azonosítót használunk, amit általában egy sorszámozott típusú attribútum, tipikusan egy egész szám reprezentál. Az objektum-orientált nyelvekben rendkívül fontos az öröklődés, ami viszont nem szerepel a relációs adatmodellben. Ennek megfelelően számos lehetőség elképzelhető – és létezik is – az öröklés relációs ábrázolására, de ezt az objektum-orientált nyelvek öröklődéskezelésének különbözőségei miatt inkább később, a tényleges implementációs környezet bemutatásánál részletezzük. Érdekes lehet megfigyelni, hogy az ORM technológiák a relációs adatbázis kezelő rendszereket az objektum-orientált adatbázis kezelő rendszerekre (Object Orientated Database 21
Management System, OODBMS) jellemző képességekkel igyekeznek felruházni. A technológia létjogosultságát pedig éppen abban érezzük, hogy míg az objektum-orientált szemléletű szoftverfejlesztés valamint a hozzá tartozó programozási nyelvek és eszközök rendkívül elterjedtek és széles körben használatosak napjainkban, addig ezzel ellentétben az OODBMS termékek még kiforratlanok és csak szűk körben használatosak szemben a piacon domináns RDBMS termékekkel. Így egy olyan technológia, amely e két szemlélet között teremt praktikus átjárhatóságot, az széles körben elterjedté válhat, hiszen lényegesen olcsóbbá teszi egy rendszer felépítését a már meglévő, sokat használt és jól optimalizált eszközök használatával. Meg kell ugyanakkor jegyeznünk, hogy ez az átjárhatóság korlátozott és kompromisszumokkal telített, ugyanakkor – mint azt később látni fogjuk – a gyakorlatban jól alkalmazható.
3.2.3. A Hibernate rendszer bemutatása Mint már említettük, az ORM technológiára egy konkrét implementáción, a Hibernate-en [9] keresztül figyeltünk fel a felhasználható technológiák számbavétele során, konkrétabban a Java alapú technológiák vizsgálata közben. A Hibernate ugyanis egy Java platformú ORM rendszer, bár .NET platformon működő verziója is létezik NHibernate néven. A Hibernate alapvetően JDBC-n keresztül kapcsolódik a relációs adatbázishoz, amelyhez tehát szükségünk van az adott RDBMS-hez tartozó JDBC driverre. A JDBC elterjedtségének köszönhetően napjainkban a népszerűbb RDBMS termékekhez létezik ilyen driver, így elmondhatjuk, hogy a Hibernate rendszer szinte minden gyakorlatban előforduló RDBMS termékkel használhatjuk. A JDBC azonban – mint minden adatbázis-absztrakciós interfész – általánosítja a különböző implementációk képességeit, ami egyrészt teljesítménybeli hátránnyal, másrészt a termékspecifikus lehetőségek elvesztésével jár együtt. A Hibernate azonban képes a különböző RDBMS termékeket számukra speciális módon vezérelni. Ezt egy beállítási lehetőségen, a dialektus (Hibernate dialect) opción keresztül érhetjük el. A Hibernate-ben számos népszerű RDBMS termékhez létezik definiált dialektus. Ezt – a JDBC kapcsolat paramétereivel, valamint egyéb specifikus beállításokkal együtt – egy konfigurációs fájlban adhatjuk meg. Java SE környezetben a Hibernate rendszer szolgáltatásainak igénybevételéhez telepítenünk kell a rendszert alkotó JAR állományokat. A korábban említett objektum-orientált/relációs transzformációt osztályonként egy-egy XML formátumú ún. Hibernate mapping fájlban írhatjuk le. Itt lehetőségünk van megadni az egyes attribútumok ún. Hibernate típusát, ami az adatbázisban használt típus és a Java nyelvi típus között teremet kapcsolatot. Itt definiálhatjuk az egyes osztályok közötti kapcsolatoknál, vagy éppen az örökléseknél használt stratégiákat. Ezen kívül megadhatjuk még az adatbázis tábláknak és azok oszlopainak a nevét, valamint egyéb korlátokat is definiálhatunk az oszlopok vagy akár a táblák szintjén is. Itt jelölhetjük meg az id-ként szolgáló attribútumot vagy attribútumokat, valamint speciális stratégiákat használhatunk bizonyos összetettebb attribútumok transzformálására. Ezen mapping XML fájlok elkészítése gyakran elég sematikus, éppen ezért előszeretettel használnak hozzá kódgeneráló eszközöket, például XDoclet-et. A tervezés ezen fázisában felmerült bennünk, hogy az adatmodellt alkotó osztályokat formálisan, UML diagram segítségével specifikáljuk modellező eszközök segítségével. Ebben a modellben szerettük volna elhelyezni a Hibernate számára szükséges transzformációs információkat is, majd az erre a célra készített programmal generáltuk minden egyes Java osztály vázát, valamint a hozzá tartozó Hibernate mapping fájlt. Ez a megközelítés azért volt szerencsés, mert a tervezésbe egyszer befektetett – talán kicsivel több – munkával az implementáció illetve a dokumentáció során rendkívül sok időt takaríthattunk meg.
22
Tárolt eljárások vagy Hibernate? Ha a Hibernate rendszert szeretnénk értékelni a PL/SQL alapú tárolt eljárásokkal szemben, a következőket mondhatjuk el: -
A tárolt eljárásokkal nagyobb teljesítmény érhető el azonos erőforrások használatával.
+
A Hibernate-tel az alkalmazás sokkal rugalmasabbá válik, illetve tetszőleges RDBMS termékkel használható egyetlen konfigurációs beállítás megváltoztatásával. Ez utóbbi az éles környezetben való használatban azonban egyáltalán nem jelentett előnyt számunkra az Oracle Database termékek iránti említett elkötelezettségünk miatt, ugyanakkor nagy könnyebbséget jelentett a fejlesztés során, amikor így nem volt szükségünk a teszteléshez egy működő Oracle Database szerverre, hanem például egy a saját számítógépünkön bekonfigurált Hypersonic SQL adatbázissal is ki tudtuk próbálni az alkalmazást a fejlesztés közben.
+
Ezen kívül a Hibernate lehetőséget biztosít az objektum-hierarchia szintjén történő keresésre az SQL-hez hasonló szintaktikájú Hibernate Query Language (HQL) segítségével. Ennek köszönhetően az üzleti logika műveleteiben is már az adatmodell objektumai szintjén gondolkozhatunk az implementáció során.
+
Megemlíthetjük még, hogy a Hibernate a transzformációs információk alapján képes automatikusan létrehozni a relációs sémákat az adatbázisban. A fenti érvek alapján a Hibernate rendszer használata mellett döntöttünk.
Hibernate Java EE 5.0 környezetben A Hibernate használatával kapcsolatos kezdeti elképzeléseink aztán jelentősen megváltoztak, amikor a Java EE, azon belül is legújabb, 5.0-ás verzió használatát fontolóra vettük. Az EJB 3.0 specifikációban megjelenő – korábban már bemutatott – perzisztencia kezelés filozófiája nagymértékben hasonlít a Hibernate perzisztencia kezelésének felfogásához. Ennek oka elsősorban az, hogy az EJB 3.0 szabvány kidolgozásában jelentős szerepet játszottak a Hibernate-et fejlesztő közösség legmeghatározóbb tagjai javaslataikkal és észrevételeikkel. A Hibernate legújabb, 3.2-es verziója már jól illeszkedik az EJB 3.0 architektúrájához, és ennek megfelelően használható EJB 3.0-ás entitás beanek perzisztencia menedzsereként Container Managed Persistence igénybe vétele esetén. A Java SE környezetben való felhasználáshoz képest ennek a megoldásnak két fontosabb előnye is van: Egyrészt az alkalmazás szerveren több példányban futó alkalmazások vagy egyéb kliensek ugyanazon az entitás halmazon dolgoznak, míg Java SE alkalmazás esetén minden futó példány a memóriában elkülönített helyen tárolja a saját entitás példányait. Emiatt könnyen erőforrás korlátokba ütközhetnénk. Másrészt az annotációk és az entitás menedzser révén olyan újítások kerültek be a Hibernate rendszerbe is, amelyek jelentős mértékben könnyítik, valamint gyorsabbá és egyszerűbbé tehetik a fejlesztés folyamatát. Ugyanakkor pedig elmondhatjuk, hogy Hibernate Java EE környezetben is tud mindent, amit Java SE környezetben, ezen felül még új lehetőségeket is nyújt – ilyen például a korábban már említett automatikus tranzakció-kezelés.
23
Az adatok érvényességének ellenőrzése Tegyünk még említést a Hibernate rendszer egy másik, általunk használt kiegészítéséről. Ez a Hibernate Validator. Használatával lehetőségünk nyílik az entitások attribútumainak automatikus validálására azáltal, hogy általános validálási mintákat rendelünk az attribútumokhoz annotációk segítségével. A mintáknak megfelelően a Hibernate Validator egyrészt legenerálja az adatbázisban a hozzájuk tartozó korlátokat, amiket az RDBMS ellenőriz. Másrészt lehetőségünk van futási időben is ellenőriztetni a rendszerrel az entitásokat, és a validálási minták megsértése esetén a rendszer megmutatja, hogy mely attribútumok értéke melyik mintát sérti. Természetesen a Hibernate Validatorban már léteznek beépített validálási minták a leggyakrabban előforduló esetekre. Így például egy karakterlánc típusú attribútumon egyszerűen az @Email annotáció elhelyezésével elérhetjük, hogy az adott attribútum csak akkor legyen érvényes, ha az egy helyes formátumú email címet tartalmaz. Ezen felül a rendszer lehetőséget biztosít saját validációs minták implementálására, amelyek ezután a rendszerbe beépülve, annak funkcióival együtt használhatók.
3.2.4. Az adatmodell réteg implementációjának bemutatása Célunk itt elsősorban nem az implementációs részletek bemutatása. Először ismertetjük azokat az elveket és választott konvenciókat, amelyek használatát a teljes adatmodell rétegben kötelező érvényűen alkalmaztuk. Ezután egy rövid példán igyekszünk bemutatni, hogyan alkalmazhatjuk az adatmodell tervezésében az implementációs környezet eddig ismertetett lehetőségeit. Döntések és konvenciók Foglaljuk össze a technológiával kapcsolatos elhatározásainkat: •
Az adatmodellt az EJB 3.0-ás specifikációjának megfelelő entitás beanekből építjük fel.
•
Az entitásokhoz tartozó transzformációs információkat az entitás osztályon és annak attribútumain elhelyezett annotációk segítségével tároljuk.
•
Az entitások automatikus validálását a Hibernate Validator végzi. Az ehhez szükséges információkat is az entitás attribútumain elhelyezett annotációkban tároljuk.
•
Minden entitás logikai id-val rendelkezik. A hozzá tartozó attribútum neve "id", és a relációs sémában a neki megfelelő oszlop neve a táblában szintén "id".
•
Az id értéke automatikusan generálódik a perzisztens entitás létrehozásakor, ehhez a Hibernate rendszer által definiált automatikus (AUTO) generálási stratégiát használjuk. Ez a stratégia a választott RDBMS termékhez legjobban illeszkedő módszert fogja választani, ami például az Oracle Database termékek esetén szekvencia típusú változó használatát jelenti.
•
A perzisztens entitás létrehozása után az id értéke már nem változtatható meg.
•
A felhasználói műveletek tranzakcionális támogatására – teljesítménybeli megfontolások miatt – az ún. optimista zárolás (Optimistic Locking) stratégiát [9] használjuk.
Az optimista zárolás abból áll, hogy minden entitáshoz rendelünk egy "version" attribútumot, amely az entitás módosításakor automatikusan növekszik. Így egy hosszadalmas, felhasználói interakciókkal teli tranzakcionális művelet során nem szükséges zárakat fenntartanunk a műveletben felhasznált entitásokra. Ehelyett a tranzakció véglegesítése előtt ellenőrizni kell, hogy a megváltoztatni kívánt entitások verziója közben 24
nem nőtt-e, és ebben az esetben a tranzakció lezárásakor minden megváltoztatott entitásnak növelni kell a verzióját. A tranzakció közben megváltozott verzió arra utal, hogy a tranzakció során használt entitás már elévült, a tranzakciót vissza kell vonni. Így tényleges zárakat csak a tranzakció véglegesítésének idejére kell fenntartanunk. Ez a stratégia akkor előnyös, ha valójában ritkán fordul elő hogy konkrét entitások konkurens módon változnak a rendszerben. Ez az állítás pedig jól illik az általunk megcélozott alkalmazások körére, ahol a felhasználók nagyrészt a hozzájuk kapcsolódó adatokat változtathatják. Megjegyezzük továbbá, hogy az ismertetett stratégia a Hibernate rendszer beépített szolgáltatása, vagyis az említett verzió attribútum definiálásán kívül semmi mást nem kell tennünk ennek megvalósításához. Most pedig vegyük sorra az implementációval kapcsolatos konvenciókat: •
Az attribútum értékét egy private mező tárolja, az érték lekérdezését és beállítását a hozzá tartozó getter/setter függvények végzik. Ez megfelel a Java környezetben elterjedt szokásoknak. Az attribútumhoz tartozó annotációkat mindig a getter függvényen helyezzük el.
•
A bemutatott konvencionális attribútumok definiálásához létrehoztunk egy Entitas nevű absztrakt osztályt, amely minden entitás bean osztályának őse lesz. Ezt az osztályt a @MappedSuperclass annotációval láttuk el, ami azt jelenti, hogy az adott osztály nem egy létező entitásnak a definíciója, de minden belőle származó osztály örökli a benne definiált transzformációs és validálási információkat is.
•
Az Entitas osztályban definiáljuk az id attribútumot, ami java.lang.Long típusú, vagyis 64 bites egész értéket tárolhat.
•
Az Entitas osztályban definiáljuk a version attribútumot, ami java.util.Date típusú, vagyis dátum-idő információt tárolhat.
•
Minden entitás bean osztálya tehát az Entitas osztályból származik.
•
Az entitások attribútumainak transzformációjának valamint az entitások közötti relációknak konkrét implementációs részleteiről lásd: EJB Persistence API [JSR220] [8] és Hibernate [9] [18].
25
3.3. Második réteg: üzleti funkciók Korábban már említettük, hogy a sémánk 4 réteget határoz meg, amelyben az előzőekben tárgyalt adatmodell rétegre az üzleti logika réteg illeszkedik.
3.3.1. A technológiáról Vizsgáljuk meg, milyen funkciókat valósít meg az üzleti logika réteg. Ez nem más, mint azon szolgáltatások összessége, amelyek megvalósítják az üzleti modellben definiált üzleti primitíveket úgy, hogy ezeket igénybe véve majd felépíthessük a különböző üzleti funkciókat. Technológiai szempontból ez a réteg is az EJB 3.0 technológiájára épül. Az említett a szolgáltatások elsősorban szolgáltatás beanekben kerülnek megvalósításra, de a speciális feladatok elvégzésére előkerülnek az üzenetvezérelt beanek, valamint az EJB specifikációjának egy másik eleme, a Timer Services is, melynek segítségével funkciók végrehajtását időzíthetjük [8]. A szolgáltatás beanek között előfordulnak állapot nélküli és állapottal rendelkező beanek is a szolgáltatások jellegétől függően. Technológiailag az üzleti logika és az adatmodell beanjei egy perzisztencia egységbe (persistence unit) vannak csomagolva, és az entitások mindig csak az adott perzisztencia egységen belülről érhetők el. Ez az EJB 3.0-ás specifikációból eredő kényszer, aminek köszönhető az a már említett tény, hogy az entitás beaneknek nem lehet helyi vagy távoli elérésű bean interfészeket definiálni. (Megjegyezzük, hogy tulajdonképpen a korábbi verziókkal való kompatibilitás megőrzése érdekében mégis lehet használni ezeket az interfészeket, ugyan nem definiálhatók szabadon.) Ennek az a koncepcionális oka, hogy az entitásokat közvetlenül kezelő szolgáltatásokat egy egységbe zárjuk, amely mind biztonsági, mind teljesítménybeli szempontból előnyös, és a megvalósíthatóságot nem korlátozza. Továbbá amennyiben biztosítani tudjuk, hogy az üzleti logika rétegben implementált szolgáltatások a rendszert helyes állapotból mindig helyes állapotba viszik át, – ahol a rendszer helyes állapotainak a perzisztens adathalmaz konzisztens állapotait értjük, – akkor garantálható, hogy a rendszert helyes állapotból indítva a perzisztens adatok soha nem kerülnek inkonzisztens állapotba. Ezt egyszerű, dekomponált részfeladatok esetén sokkal könnyebb ellenőrizni/tesztelni, mint kevésbé strukturált feldolgozás esetén. A szolgáltatások szervezése Vizsgáljuk meg most közelebbről az üzleti logika réteg felépítését. Az itt jelen lévő osztályokat és interfészeket megkülönböztethetjük az előfordulásuk alapján az alábbiak szerint: a külső – vagyis a kliens oldalon is jelen levő – és a belső típusok. Az első kategóriába tartoznak a távolról elérhető szolgáltatásokat leíró interfészek, valamint az ún. üzleti kivételek. Ezek olyan kivételek, amelyeket az üzleti logika réteg távolról hozzáférhető szolgáltatásai válthatnak ki, és amelyekről a hívó félt értesíteni kell. Természetesen ide tartozik még a teljes adatmodell réteg, vagyis az entitás beanek is, bár ezek nem az üzleti logika réteg részei. Az említett osztályokat és interfészeket egy külön csomagban – kliens JAR fájl – az üzleti réteget elérni kívánó alkalmazások számára elérhetővé kell tenni. Az alkalmazás szerveren futó JAR fájlba – ami tehát a perzisztencia egységet definiálja – mind a külső, mind a belső típusok bekerülnek. A belső típusok közé tartoznak a beaneket implementáló osztályok, a belső kivételek, bármilyen egyéb a belső feldolgozás során
26
használt osztály, valamint azok a bean interfészek is, amelyek a csak helyileg elérhető szolgáltatásokat definiálják. A helyi szolgáltatásokról Ezek olyan szolgáltatások, amelyek más beanek számára biztosítanak primitíveket a saját műveleteik felépítéséhez. A megvalósított üzleti rétegben ilyen például a jelszó kódolása. Ez egy adott karakterláncot titkosít – például egyirányú hash-eléssel –, amit a felhasználók jelszavának eltárolásához használhatunk fel. Természetesen ennek a szolgáltatásnak nem kell távolról elérhetőnek lennie, hiszen ha például a kliensnek kódolná le a felhasználó jelszavát, akkor tisztában kellene lennie a pontos kódolási eljárással. Ez egyrészt biztonsági kockázatot is jelent, másrészt többletmunkát igényelne és jelentősen megnehezítené, ha a kódolási eljárást később le szeretnénk cserélni. Ilyenkor célszerű helyi szolgáltatásokat definiálni. Ez a szemlélet is a modularitásra, a komplexebb feladatoknak a részfeladatokra bontására inspirálja a fejlesztőt. Sőt, – mint később látni fogjuk, – több különböző implementációjú, de azonos feladatot ellátó szolgáltatás is lehet egyidejűleg a rendszerben, és könnyedén konfigurálhatjuk azt, hogy az egyes esetekben ezek közül melyik kerüljön felhasználásra. A szolgáltatások elérése Ezzel rá is tértünk az üzleti logika réteg bemutatásának következő fázisára, a szolgáltatások címtárban történő kezelésére. Java EE környezetben a Java Naming and Directory Interfaces (JNDI) címtárat használhatjuk a szolgáltatásaink nyilvántartására. Ebben tetszőleges erőforrást regisztrálhatunk, – természetesen jogosultságokat is rendelhetünk hozzájuk, – majd kliensként megcímezhetjük a őket. Ez kényelmes támogatást biztosít az alkalmazásban lévő komponensek egymáshoz illesztésének konfigurálására. Vizsgáljuk meg az előzőleg említett példát: a jelszó kódolását. Tegyük fel, hogy a rendszerben szereplő felhasználók egy részhalmaza számára szeretnénk biztosítani, hogy a jelszavuk visszaállítható legyen a kódolt formából, míg a többi felhasználó esetén egyirányú titkosítási eljárást alkalmazunk – melynek hatására legfeljebb a jelszó törlésére van lehetőség annak elfelejtése esetén. Ehhez két különböző kódoló bean implementációt készítünk, ezek neve legyen most rendre EgyiranyuKodolo és KetiranyuKodolo. Mindkét bean azonban – legalább is a kódolás végrehajtása szempontjából – egyformán kezelhető, vagyis egy közös Kodolo interfészen keresztül hívható meg a String kodol(String kodolandoSzoveg) szintaktikájú metódusuk. Ezek után tételezzük fel, hogy az egyirányú kódoló esetén ezt a szolgáltatást a java:/comp/env/ejb/EgyiranyuKodolo JNDI cím alá regisztráljuk be, míg a kétirányú kódoló esetén hasonlóan, a java:/comp/env/ejb/KetiranyuKodolo cím alatt tesszük ugyanezt. Innentől kezdve például a jelszó beállítása előtt csak el kell döntenünk, hogy az adott felhasználó jelszavát melyik módszerrel akarjuk titkosítani a tároláshoz, majd az ennek megfelelő JNDI címről elkérni a Kodolo interfészben definiált szolgáltatást, és végül kódolni vele a megadott jelszót. Megjegyezzük, hogy ilyenkor a felhasználó típusának megfelelő kódoló szolgáltatás JNDI címének kiválasztását célszerű egy szolgáltatásként egy beanben megvalósítanunk a modularitás lehetőségeinek teljes kihasználása végett. A távolról is elérhető szolgáltatások igénybevétele esetén a kliensalkalmazás ugyanígy, a JNDI címtáron keresztül érheti el a kijelölt szolgáltatásokat. Itt természetesen már előfordulhat, hogy az elérés hálózaton keresztül megy végbe, ami azonban a fejlesztő szempontjából teljesen átlátszó módon történik – mindössze egyszer kell definiáljuk, hogy milyen hálózati címen érhető el az alkalmazás szerver. 27
Láthatjuk tehát, hogy ennek a technológiának a használatával könnyedén rakhatjuk össze az alkalmazásunkat kis, egyszerű részfeladatokat ellátó egységekből, amelyek helyességét sokkal könnyebben tesztelhetjük, ellenőrizhetjük.
3.3.2. Az implementációról A továbbiakban az eddig megismert lehetőségek implementációs részleteit vizsgáljuk. Az EJB 3.0 szolgáltatásainak igénybevétele Az EJB 3.0 által nyújtott szolgáltatásokat valamint egyéb erőforrásokat a Java EE 5.0 környezeten belül az ún. injekció (injection) technika segítségével érhetjük el legkényelmesebben [8]. Ez a következőképpen történhet: A bean osztályban deklarálunk egy mezőt, ami az elérni kívánt szolgáltatást biztosító osztály vagy interfész egy példányát tárolhatja. Ezen a mezőn elhelyezünk egy megfelelő annotációt, amely leírhatja, hogy hol érhetjük el az adott szolgáltatást, vagy milyen paraméterekkel kívánjuk használni. Amikor a bean egy példánya egy kiszolgálandó kéréshez rendelődik, az alkalmazásszerver automatikusan inicializálja a megadott mezőt az annotációban elhelyezett információk alapján. A erőforrás típusát a rendszer az annotáció vagy a mező típusából találja ki. Az erőforrás elérési címét és paramétereit pedig a rendszer vagy alapértelmezett értékek, vagy az annotációban megadott információk alapján deríti ki. Ezek alapján lássunk egy konkrét példát a korábban már említett entitásmenedzser interfész (EntityManager) elérésére injekción keresztül: @Stateless public ValamiEJB { @PersistenceContext private EntityManager em;
}
public void csinalValamit() { ... }
A fenti kódban a csinalValamit() eljárás belsejében az em változó már inicializálva van, és az adott perzisztencia egység entitásmenedzsere érhető el rajta keresztül. Ehhez hasonlóan az EJB annotáció segítségével hozzáférhetünk már beanekhez, valamint a @Resource annotációval több erőforráshoz, mint például: közvetlen JDBC adatbázis kapcsolathoz, felhasználói tranzakcióhoz, a bean kommunikációs környezetéhez, stb. Az injekciók használata mellett természetesen továbbra is lehetőség van a hagyományos módon, programkódból, JNDI címtárbeli kéréseken keresztül elérni az egyes erőforrásokat. Ilyenkor a bean osztály metódusaiban használat előtt saját magunknak kell inicializálnunk az erőforrásokra hivatkozó objektumpéldányokat. EJB 3.0 Persistence API a gyakorlatban Ismerkedjünk meg először az EJB 3.0 entitásainak életciklusával! Egy entitás-életciklus három szakaszból áll: •
menedzselt vagy perzisztens: ilyenkor az entitás egy a perzisztens környezetben létező entitást reprezentál. Perzisztens környezeten a perzisztens entitások halmazát értjük. Attribútumainak megváltoztatása – minden egyéb tevékenység nélkül – maradandó, vagyis perzisztens változást jelent – természetesen ez csak a tranzakció sikeres lezárása esetén válik véglegessé.
28
•
nem menedzselt: ilyenkor az entitás létezik – vagy legalább is valamikor létezett – a perzisztens környezetben, de attribútumainak értéke nem feltétlenül egyezik meg a perzisztens entitás attribútumainak értékével. Attribútumainak megváltoztatásának hatása nem perzisztens, vagyis nem jelenti – automatikusan – a perzisztens entitás attribútumainak megváltozását.
•
nem létező: az entitáspéldány létrehozása után, de állapotának perzisztálása előtt tartózkodik ebben a szakaszban.
Ez utóbbi két szakaszra együttesen tranziens szakaszként is hivatkozunk, ami azt szemlélteti, hogy ilyenkor az entitás állapotának megváltoztatása nem maradandó, vagyis tranziens, míg menedzselt állapotban minden változtatás maradandó, vagyis perzisztens. Foglaljuk össze most azokat a legfontosabb műveleteket, amelyek segítségével az üzleti logika réteg hozzáférhet a perzisztens entitásokhoz. Ezeket a műveleteket az EntityManager interfészen keresztül vehetjük igénybe, amit az előző részben bemutatottat alapján érhetünk el. Az alábbiakban bemutatjuk ennek legfontosabb metódusait: •
Nem létező entitást a void persist(Object) metódus segítségével perzisztálhatunk.
•
Perzisztens entitást a T find(Class>, Object) metódussal kereshetünk meg, ahol a metódus első paramétere a keresett entitás osztályát, második paramétere elsődleges kulcsát adja meg.
•
A void remove(Object) eljárás törli a paraméterként átadott perzisztens entitást.
•
A T merge(T) metódus segítségével a paraméterként átadott tranziens entitás állapotát menthetjük el a perzisztens környezetbe. A metódus a megadott entitás perzisztens példányával tér vissza.
•
A void clear() metódus segítségével az entitásmenedzser által menedzselt entitáspéldányok nem menedzselt állapotba kerülnek át, így a rajtuk végzett módosítások már nem lesznek hatással a perzisztens környezetre.
•
A javax.persistence.Query createQuery(String) metódus segítségével a paraméterben definiált EJBQL lekérdezést reprezentáló objektumot kapunk vissza, aminek segítségével aztán a lekérdezés a perzisztens környezetben végrehajtható, és belőle annak eredménye kinyerhető. Az EJBQL – EJB Query Language – egy az SQL-hez hasonló szintaktikájú lekérdező nyelv, amelyben az entitások szintjén fogalmazhatunk meg lekérdezéseket.
•
A javax.persistence.Query createNamedQuery(String) metódussal annotációkban definiált, előre fordított nevesített lekérdezéseket érhetünk el. A nevesített lekérdezések előnye az, hogy egyrészt a programnak az alkalmazásszerverre való feltöltésekor, egyszer kerülnek fordításra, másrészt a perzisztencia egységen belül bármelyik beanben elérhetők.
Az itt megismertetett eljárások segítségével vezérelhetjük az adatmodell réteg entitásainak, vagyis a perzisztencia környezetnek az állapotát. Ennek segítségével építhetjük tehát fel az üzleti logika réteg primitívjeit. Természetesen a szolgáltatások köre ennél sokkal bővebb, ezen szakasz célja csak a betekintés nyújtása volt. A részleteket megtaláljuk az EJB 3.0 Persistence API-ban [JSR220].
3.3.3. A konfiguráció Vessünk egy pillantást az adatmodell és az üzleti logika réteg futtatókörnyezetbeli konfigurációjára. Ezt elsősorban a korábban már említett persistence.xml állomány 29
határozza meg, amit az alkalmazásszerveren futtatott JAR csomagban, azon belül a METAINF könyvtárban kell elhelyezni. A fejlesztés során használt konfiguráció például a következőképpen néz ki: <provider>org.hibernate.ejb.HibernatePersistence <jta-data-source>java:/DefaultDS <properties> <property name="hibernate.dialect"> org.hibernate.dialect.HSQLDialect <property name="hibernate.hbm2ddl.auto">update <property name="hibernate.show_sql">true <property name="hibernate.format_sql">true
Nézzük meg sorban, mit is jelentenek az itt megjelenő információk: •
A persistence-unit XML tag-nek a name attribútuma a perzisztencia egység nevét definiálja. Ezzel lehet hivatkozni a perzisztencia egységre, ugyanis egy persistence.xml állomány több ilyet is definiálhat.
•
A provider tag mondja meg, hogy az alkalmazásszerveren elérhető perzisztenciamenedzserek közül melyik kezelje a definiált perzisztencia egységet. Ez esetünkben a Hibernate rendszer.
•
A jta-data-source tag azt mondja meg, hogy a perzisztencia-menedzser melyik adatforrásban (Data Source) tárolja a perzisztens adatokat. Ez itt egy JTA adatforrás JNDI nevét jelenti, ami annyit jelent, hogy az adott adatforrás kompatibilis a Java Transaction API-val, ezért az alkalmazásszerver képes vezérelni azt. Ilyen adatforrásokat az alkalmazásszerveren kell definiálnunk, ami a különböző implementációk esetén eltérő módon történhet. Az általunk használt rendszerben ennek módjára példát mutatunk a függelékben.
•
A properties csomóponton belül a perzisztencia-menedzsernek adhatunk át implementáció specifikus beállításokat. A fenti példában szereplő opciók az általunk használt Hibernate rendszer számára nyújtanak információkat a következők szerint:
Az első opció a már említett Hibernate dialektust definiálja, ami esetünkben a fejlesztés szakaszában használt Hypersonic SQL adatbázis kezeléséhez tartozó dialektus volt.
A következő beállítás a relációs sémák – vagyis a táblák, az indexek, a korlátok és egyéb adatstruktúrák – automatikus generálásának stratégiáját írják elő. Az itt beállított update érték azt mondja meg, hogy az adatbázisban már esetleg létező sémát módosítsa a rendszer szükség esetén úgy, hogy az tárolni tudja a definiált entitásokat. Ilyenkor a meglévő táblák adatai nem vesznek el, de a nem létező táblákat létrehozza a rendszer. Éles környezetben célszerű használni itt a validate opciót, ami semmilyen esetben nem módosítja az adatbázisban tárolt sémát, de az alkalmazás feltöltésekor ellenőrzi annak helyességét, és hiba esetén az alkalmazást nem engedi betöltődni. Ilyenkor az adatbázisban tárolt korábbi adatokat a rendszer nem engedi módosítani vagy kitörölni az alkalmazás frissítésekor.
30
Az utolsó két opció a Hibernate-et arra utasítja, hogy az adatbázis felé elvégzett SQL utasításokat jelenítse meg az alkalmazásszerver kimenetén, formázottan.
Más perzisztencia-menedzsereknek természetesen más paramétereit lehet itt állítani. A részleteket az adott implementáció dokumentációiban keressük.
Fontos megjegyeznünk, hogy a fenti konfigurációs állomány a konkrét futtatási környezettől, vagyis az alkalmazásszervertől független. Felépítése, és az ismertetett információk kezelése az EJB 3.0 specifikációjában rögzített, így minden kompatibilis alkalmazásszerver megérti.
31
3.4. Vezérlés és megjelenítés Jelen fejezetben ismertetjük az elterjedt keretrendszerek vizsgálatakor szerzett tapasztalatainkat. Nem célunk a keretrendszerek teljes körű bemutatása. Szempontunk teljes mértékben gyakorlatias, alapvető kérdésünk egy keretrendszerrel szemben, hogy milyen könnyebbségeket és nehézségeket jelent számunkra a használatával való fejlesztés. Felvetünk egy nagyon egyszerű üzleti alkalmazást, amely a vizsgált keretrendszerek használatával implementálásra került, majd bemutatjuk azon fontos momentumokat – a megfelelő kódrészletekkel illusztrálva –, melyek bemutatják a rendszerek alapvető sajátosságait. A fejezet első pontjaiban a webes alkalmazások általános szerkezeti kérdéseit taglaljuk, második felében térünk rá a konkrét keretrendszerekre, végül pedig röviden bemutatjuk saját koncepciónkat. Az Olvasóról feltételezzük, hogy bizonyos szinten járatos a webes alkalmazások fejlesztésében, de legalábbis a programozásban. Tanulmányunkat úgy szerkesztettük, hogy hasznos információval lássuk el azon esetre, amennyiben webes alkalmazás fejlesztéséhez architektúrát, keretrendszert választ vagy dolgoz ki, legyen programozó, projektvezető vagy menedzser. Ilyen jellegű háttérismeret nélkül is azonban kiolvasható írásunkból a szoftverarchitektúrák fejlődése, értékelése.
3.4.1. A példaalkalmazásunk Az architektúrák sajátosságainak szemléltetésére egy általános példaalkalmazás lesz a tárgyunk. Legyen egy egyszerű webáruházunk, amelyben a vásárló adott termékekből összeállíthatja kosarát, majd megadhatja szállítási adatait végül megerősítheti a rendelést. Adatbázisunkban külön entitásokként tároljuk a termékeket, a vásárlást (számlát), és a számla tételeit. ER diagramunk a 3. ábrán látható.
3. ábra – A példaalkalmazás Entitás-relációs diagramja
A munkafolyamatunk az alábbi állomásokból áll, a megadott lépéslehetőségekkel: •
Polc, a termékek listája: termékválasztás, kosár megtekintése
•
Mennyiség, a termék adatai: mennyiség megadása vagy visszalépés
•
Kosár, tételek listája: tétel törlése, új termék választása vagy teljes törlés
•
Számlázási adatok megadása: elfogadás, visszalépés, vagy teljes törlés
•
Számla megtekintése: megerősítés vagy elvetés 32
A fent megadott képernyőkkel szemben az alábbi követelményeket támasztjuk. Az alábbi specifikációban megfogalmazott követelményekre később, a keretrendszerek tárgyalásakor a zárójelben feltüntetett kódokkal fogunk hivatkozni. Polc A vásárló a polcról válogathat a boltban kapható termékek közül. A oldal tartalmazza rendezetten minden termék megnevezését, árát és a belőle való raktárkészletet (P1). Legyen lehetőség egy termék választásával a Mennyiség megadása (P2) és a Kosár megtekintése képernyőre továbblépni (P3). Mennyiség megadása A vásárló itt adhatja meg, hogy a választott termékből hány darabot szeretne rendelni. Kerüljön a képernyőre a termék megnevezése, ára és a rendelkezésre álló raktárkészlet (M1). Bemenetként a kívánt mennyiséget adjuk meg (M2). A mennyiség akkor érvényes, ha pozitív (M3) és nem nagyobb, mint az aktuális raktárkészlet (M4). Érvénytelen mennyiség esetén erről kapjunk hibaüzenetet (M5). Legyen lehetőség a művelet megszakítására (M6), tehát amikor a tételt mégsem adjuk hozzá, kerüljünk vissza a polchoz. Legyen lehetőség a tétel tényleges hozzáadására, ekkor a Kosár megtekintése képernyő következzék (M7). Kosár megtekintése A vásárló itt láthatja, mely termékekből és mennyit választott eddig a kosarába. Kerüljön a képernyőre minden tételről a termék megnevezése, a termék egységára, a kért mennyiség és a tétel összértéke (K1). Tétel választása után kérheti annak törlését (K2). Kérhessük a kosár teljes törlését (K3), térhessünk vissza a polchoz (K4). Üres kosár esetén "a kosara üres" üzenetet lássuk (K5). Az itt megadott egyszerű alkalmazás előnye a vizsgálatok során, hogy szerkezete minimálisan egyszerű, valamint a teendőivel és fogalmaival a köznapi életből mindenki tisztában van. A példánk tartalmazza a webes üzleti alkalmazások által igényelt legalapvetőbb építőelemeket. Ezek: •
üzleti adatok csoportosított kiírása
•
adat bekérése
•
a bekért adat validációja, hiba esetén a beviteli űrlap automatikus visszaadása, a hiba helyének és okának jelzése
Követelményünk ezen kívül, hogy az alkalmazás munkamenetének logikai szerkezete a programkódban is tükröződjék. A továbbiakban egy weboldal szerveroldali programkódjának alapvető szerkezeti mintáit tárgyaljuk.
3.4.2. Nem strukturált végrehajtás Mint a technológiaválasztásnál említettük, egy kérés kiszolgálását első megközelítésben a kérés értelmezése, üzleti műveletek elvégzése, kimeneti adatok kiválasztása és a kimenet előállítása részfeladatokra bonthatjuk. A nem strukturált végrehajtás esetén semmiféle szervezéssel, megkötéssel nem élünk a kérést kiszolgáló kóddal szemben. Ezt szemlélteti a 4. ábra. 33
Kiszolgálás
HTTP kérés
Adatbázis
Fájlrendszer
HTTP válasz
[…] szolgáltatások
4. ábra – A nem strukturált végrehajtás
Szemléltetésképpen képzeljük el, hogyan nézne ki például a termékválasztó képernyőt előállító (pszeudo) PHP kód:
Ez a megvalósítás természetesen kétrétegű architektúrában érvényesül. Nem szakmai körökben igen közkedvelten készítenek ilyen módon PHP alapú weboldalakat. Könnyen és gyorsan szerkeszthető össze így egy kis funkcionalitással rendelkező honlap, megspórolva a termék szerkezeti tervezésének lépéseit és előnyeit is.
3.4.3. Szekvenciális szétválasztás Már az egy nagyon hálás lépés azonban, ha az egyszálú végrehajtásban legalább a megfelelő sorrendben végezzük a logikailag már elkülönített részeket. Tehát először értelmezzük a kérés paramétereit, majd csak utána végzünk feldolgozást. Ugyanígy, a kimeneti adatok összeállítása után állítunk elő a kimenetet, tehát a HTML előállításakor már nem hajtunk végre például SQL kérést. HTML előállítása
HTTP válasz
Kimeneti adatok
Értelmezés Paraméterek
HTTP kérés
Feldolgozás
Paraméterek
Adatgyűjtés
[…] szolgáltatások
Adatbázis
5. ábra – A kiszolgálás menete szekvenciális szétválasztás esetén.
34
Az 5. ábrán már kitűnik, hogy az első három kiszolgálási lépés implementációjakor felesleges szkriptnyelveket használnunk. Kis magyarázatra szorulnak az Értelmezés, Feldolgozás és Adatgyűjtés lépései. Az értelmezéskor a HTTP kérésből, tehát a kért URL-ből, annak paramétereiből és az esetleges POST paraméterekből kivesszük a szükséges adatokat. Feldolgozáskor módosításokat végzünk az üzleti adatokon, tehát például tételt veszünk fel, tételt törlünk. Adatgyűjtéskor a válaszban megjelenítésre szánt adatokat válogatjuk le, tehát például a számla tételeinek adatait, a számlázási adatok vagy a termékek listáját.
3.4.4. JSP bevezetése Amennyiben Java Servlet programozásakor a legegyszerűbb adódó módon állítjuk elő a HTML kimenetet, igen nehezen kezelhető kódot kaphatunk: PrintWriter pw = response.getWriter(); pw.println(""); pw.println("
Elsősorban ez motiválta a Java Server Pages megjelenését. JSP használata esetén mindezt PHP-hoz hasonlóan programozva egy template-ben valósíthatjuk meg:
Ily módon sokkal átláthatóbb és könnyebben kezelhető a kimenet előállítását végző programkód. A HTML template-ben külön névtérben helyezkednek el speciális elemek (c:forEach). Igen gyakran használt funkciókat a JSTL (Standard Tag Libraries) gyűjtemény tartalmazza négy névtérben. Ezentúl az alkalmazás fejlesztőjének lehetősége van saját Tag Library definiálására, egy Tag Library Descriptor (TLD) megadásával és a funkciók megírásával. Az elterjedt webes keretrendszerek saját tagkönyvtárakkal segítik a fejlesztők munkáját, ezeket a további fejezetekben látni fogjuk. Szemben a szkriptek világával, a JSP a látszat ellenére nem szkript. Egy JSP oldalt a webszerver java forrásfájllá alakít, majd az ebből fordított Java binary betöltése és lefuttatása adja a kívánt működést. Java EE használatakor építhetünk alkalmazást pusztán JSP-re épülve, ekkor az alkalmazás a 6. ábrán vázolt architektúrában működik. Ebben a rendszerben a JSP oldal bemeneti paramétere a HTTP kérés. A JSP oldal hozzáfér a kérés adataihoz (Request Context), a folyamat adataihoz (Session Context), valamint az egész webszerverre nézve globális adathalmazhoz (Server Context). További konfigurációval ún. Backing Bean-ek a JSP oldal "mögött" működve szolgáltathatják az üzleti adatokat, bár lehetőség van a JSP oldalból közvetlen adatbázis-elérésre is. A JSP emellett HTML-t előállító technológiaként szolgál az összetettebb architektúrájú rendszerekben. JSP alapú szoftveren alapvetően a fenti architektúrát értjük, amelyben központi szerepe van a JSP technológiának. 35
1 Kérés
JSP Böngésző
4. Válasz
DB 2 3
Modell
Adatbázisok, Vállalati szolgáltatások
Alkalmazásszerver
6. ábra – A Model 1 (kétrétegű) architektúra.
3.4.5. MVC Az MVC a Model View Controller betűszava. Ez egy általános architektúra felhasználói felületek tervezéséhez, ugyanúgy érvényesülhet asztali alkalmazás vagy annak egy moduljának fejlesztésekor, mint a webfejlesztésben. A Javás webfejlesztési világban Model 2 architektúra néven is ismert [19], míg az előbbi JSP-s megoldásra Model 1 néven hivatkoznak. A Model 2 koncepciója, hogy a vezérléshez, a megjelenítéshez valamint az üzleti adatokhoz kapcsolódó objektumokat elkülönítsük – szkript alapú rendszer esetén pedig ezek a programkódok legalábbis elkülönítve szerepeljenek. Ezt mutatja be a 7. ábra. 1 Kérés
Vezérlő
2
Modell
(Servlet) Böngésző
5 Válasz
(EJB)
DB
3 4
Megjelenítés (JSP)
Alkalmazásszerver
Adatbázisok, Vállalati szolgáltatások
7. ábra – A Model 2 (MVC, háromrétegű) architektúra.
Nézzük meg az elv érvényesítését most egy konkrét példán. K2 követelményünk szerint egy felvett tételt törlését kérhetjük. A folyamat során töröljük a tételt a tételek listájából, előállítjuk a tételek listájából a megjelenítendő információt, majd ezek segítségével elkészítjük a tényleges kimenetet. Model 1 architektúrában a tétel törlése, vagy legalábbis a tevékenység kezdeményezése a JSP oldalban kell, hogy történjék. Model 2 esetén a kérést (vagy annak egy absztrakcióját) az illetékes vezérlő objektum kapja meg. A vezérlő elvégzi a szükséges adatmódosítást, összegyűjti a kimenetre szánt adatokat, majd átadja őket a megjelenítést végző objektumoknak. A HTTP választ a megjelenítési réteg állítja elő.
36
E meggondolás ad egyrészt alapvetően lehetőséget a megjelenítéstől függetlenített alkalmazás fejlesztéséhez, legfőbb előnye mégis a programkód és az objektumok szerepeinek tisztább megadása és használata, amely alapvetően javítja a szoftver minőségét.
3.4.6. Felhasználói interakciók Azt már nagyjából láttuk tehát, hogy a kimeneti adatot miképpen öltöztetjük fel egy weboldallá. Kérdéses még azonban, hogy miképpen kezeljük a felhasználói interakciókat. Webes környezetben a felhasználó (a kliensoldali alkalmazás) egyféleképpen szólíthatja meg a szervert, HTTP kérésen keresztül. A HTTP kérésben paramétert alapvetően URL-ben és a POST változókban adhatunk át. Felhasználói interakcióknak alapvetően három típusát különböztetem meg: •
egyszerű nézetváltási felhasználói interakció kezelése (viewCart)
•
adatmódosítással járó interakció kezelése (addToCart)
•
adatbevitel kezelése (registerUser) Jól bevált konvenciók szerint a fenti feladatokra az alábbi URL-ek adnak jó példát:
Az URL-nek lesz tehát része a megszólítandó oldal neve és a tevékenység paraméterei. Ugyanezt azonban igen sokféleképpen megvalósíthatjuk, e konvenciók követése viszont kényelmes, és könyvjelzők elhelyezését is könnyen lehetővé teszi. Mint látni fogjuk azonban, egy jól átgondolt keretrendszerben nekünk URL-ekkel nem kell foglalkoznunk. Az URL-re vonatkozó konvenciók a keretrendszer döntései, mi a keretrendszer által definiált absztrakt fogalmakkal dolgozunk, erre azonban várnunk kellett.
3.4.7. Apache Struts Az Apache Struts az első webes keretrendszerek egyike. A tesztelés során az 1.2.9-es verziót használtuk. 2.0-ás változatával ugyan már próbálkozhatunk, de dokumentáció gyakorlatilag nincs hozzá. Első szembetűnő hátránya a rendszernek, hogy a projekt honlapján elérhető felhasználói kézikönyv [22] igen szűkszavú, és az ott található információ nem elégséges a fejlesztés megkezdéséhez. Egy külön „tutorial”-t voltunk kénytelenek keresni hozzá [21], amely be is vallja, hogy a Struts valóban rosszul dokumentált és nehezen tanulható. Ettől azonban önmagában még nem riadtunk vissza. Az alkalmazás struktúrája A Struts action-centrikus keretrendszer, központi fogalma tehát a felhasználói interakció. Egy akciót egy org.apache.struts.action.Action leszármazott szolgál ki, execute metódusa megkapja a HTTP kérést és választ lefedő HttpServletRequest és HttpServletResponse példányokat, kap egy ActionMapping példányt, amellyel a konfigurációs fájlban megadott akcióleíráshoz fér hozzá, továbbá megkapja a kéréshez tartozó ActionForm példányt, amelyben a felhasználó által megadott adatok lehetnek. A metódus visszatérési értéke az interakció kimenetelét tükrözi, egy ActionForward példányban. A lehetséges kimeneteleket szintén a konfigurációs fájlban kell megadnunk. Egy-egy kimenetelhez egy-egy JSP oldal tartozik.
37
Adatbázis
Konfigurációs fájl
Struts
HTTP kérés
(dispatcher)
Action Osztály (vezérlés)
+ Megjelenítendő adatok
Böngésző
HTTP válasz
JSP
HTTP kérés
(megjelenítés)
ActionForward
HTTP kérés
(modell)
Struts (view resolver)
8. ábra – Egy kérés kiszolgálása Apache Struts esetén.
A rendszer gyenge pontjai – kiírás és visszahívás Nézzünk most meg egy konkrét példát. A Polc képernyőt megjelenítő action konfigurációja az alábbi:
Melyben tehát megadjuk a kiszolgáló osztályt és megadunk egy sikeres továbblépési lehetőséget. Az kiszolgáló execute metódus törzse: request.setAttribute("termekek", getLogic().getTermekek() ); return mapping.findForward("success");
A kérés attribútumának megadjuk a termékek listáját, hogy ahhoz a JSP oldal hozzáférjen:
order
A fenti JSP oldalban két fontos teendő megvalósítása szerepel, az adatkiírás és a visszahívás. A Struts három alapvető taglibbel támogatja a JSP oldalak készítését, ezek a Logic, Bean és Html taglibek. A fenti példában a Logic egyik tagjával iterálunk végig a 38
termékek listáján, a Bean egy tagja segítségével férünk hozzá egy entitás egyik attribútumához, majd a Html link tagja segít minket némileg a visszahívás megadásában. A Bean taglib vizsgálatakor azt találtunk azonban, hogy minden, amire lehetőségünk van, az egy bean egy attribútumának kiírása, ennél komplexebb kifejezések nem adhatók meg. A visszahíváskor, a link tag az alábbi URL-t generálja nekünk: http://localhost:8080/struts-test/Mennyiseg.do?termek=1
Tehát át tudjuk adni a választott termék azonosítóját, amelyet a Mennyiseg Action az alábbi módon dolgoz fel: Long termekId = Long.parseLong(request.getParameter("termek")); Termek termek = getLogic().findTermek(termekId);
Tehát az URL-ben kapott paramétert fel kell dolgoznunk, majd vissza kell keresnünk a kérdéses terméket. Nekünk valójában viszont arra van szükségünk, hogy a egyes termékek rendeléséhez tartozó Action feldolgozásakor álljon rendelkezésre a termék maga. Kiforrottabb keretrendszereknél ehhez már sokkal kevesebbet kell dolgoznunk. Action orientáltság és biztonság A Struts azáltal, hogy központi fogalmának az Actiont tette, természetesen megköti a kezünket az alkalmazásunk strukturálásakor. Az Action-ök köré épített struktúra azonban egy lényeges gyenge ponttal rendelkezik. Mint fent láttuk, a mennyiség megadására szolgáló lapot megjelenítő Action paraméterben kapja a termék azonosítóját. Ez alapvető biztonsági fenyegetést jelent. Kezelnünk kell azt az esetet, amikor a kapott paraméter nem értelmezhető számként, valamint amikor nem azonosítója terméknek. Egy komplexebb rendszerben akár azt is vizsgálnunk kellene, hogy az aktuális felhasználó valóban jogosult-e az adott azonosítójú termék választására. Hiba esetén nyilván nem szeretnénk, ha a felhasználó egy Java hibajelentést kapna, hanem például vissza szeretnénk irányítani a Polc oldalra, esetleg egy hibaüzenetet feltüntetve az oldalon. Mivel azonban a Polc oldal megjelenítéséhez szükség volt a request.setAttribute("termekek", getLogic().getTermekek() );
műveletre, hogy a termékek listája rendelkezésre álljon, ezt a műveletet a Mennyiség Action kódjában is el kell helyeznünk, vagy egy központi helyre kell tennünk a Polcot megjelenítő metódust. További lehetőségünk a JSP oldalba illeszteni a termékek listájának lekérést, ez azonban súlyos megsértése az MVC elvnek, és nem visz minket közelebb a célunkhoz, egy egyszerű modellel összefogható alkalmazásstruktúra megalkotásához. Ezen a ponton tehát elvetettük az Apache Struts használatát és további vizsgálatát, az adatbevitelt, validációt valamint az alkalmazás állapotának megtartásának kérdéseit itt nem is vizsgáltuk tovább.
3.4.8. Tapestry A Tapestry szintén egy Apache projekt [23]. A teszt során a 4.0-ás verziót vizsgáltuk meg, 4.1-es változata 'nem stabil' címkével van ellátva, 5-ös változata még nem kiadott. A rendszer használatának kényelmét nagyban növeli, hogy a Java 5 nyelvi újítását, az annotációkat használhatjuk benne, így sok leíró információ a konfigurációs fájlokból a vonatkozott kódbeli elem mellé kerülhet. Az egyes oldalakat itt egy-egy Java osztály támogatja. A keretrendszer sok ponton kényelmi funkciókkal segít minket ezen osztály megírásában. Az osztályokat absztraktként 39
deklarálva bizonyos metódusokat a keretrendszer implementál a Javassist rendszer segítségével. Ez futási időben ad lehetőséget a lefordított Java kód módosítására. Így nekünk az osztály egyfajta vázát kell csak megírnunk. Szemben a Struts megközelítésével, itt egy oldalhoz tartozik egy Java osztály, és az oldalhoz tartozó Action-ök kiszolgálói a lap osztályának metódusai. Egy oldalhoz tehát egy Java osztály és egy html template tartozik. Ezek összerendelése automatikusan történik, a konfigurációs fájlban csupán a Java osztályok közös package nevét kell megadnunk. Page Pool Page
Tapestry dispatcher
Page
Page
Page
Action()
Action()
atai Kérés ad
Page Adatfeltöltés
HTTP kérés
Page
Page
Böngésző Adatlekérdezés
HTTP válasz
HTML template
Page
Tapestry view resolver
9. ábra – A kiszolgálás menete Tapestry rendszerben.
Kiírás A Polc oldalon az adatok megjelenítéséhez (P1 követelmény) az alábbiakat implementálnunk a Polc.java-ban: public List getTermekek() { return getLogic().getTermekek(); }
A HTML template megfelelő részlete pedig az alábbi:
A Tapestry alapvetően nem JSP technológiával dolgozik. A html template megalkotásakor itt nem taglib-ekkel dolgozunk, hanem a jwcid attribútum által jelölt komponensekkel. A beszúrandó értékeknél szereplő OGNL kifejezéseket egy harmadik fél 40
által kiadott könyvtár dolgozza fel. Ezek az Object Graph Navigation Language kifejezések, igen széleskörű lehetőségeket adnak [24]. Visszahívás A fenti példában látható a @DirectLink komponens használata. Mint említettük, a Tapestry nem Action hanem Page orientált. Ezen Action-t tehát szintén az oldalhoz tartozó Java osztály kezeli, az alábbi metódussal. public IPage order(Termek termek) { Mennyiseg mennyiseg = getMennyiseg(); mennyiseg.setTermek(termek); return mennyiseg; }
Mint látjuk, az itt megadott metódusban paraméterként kapjuk a terméket magát, mindez továbbá semmiféle konfigurációt nem igényelt. Amennyiben egy ilyen Listener metódus IPage példánnyal tér vissza, úgy a keretrendszer ezen oldalt hozza be válaszként (P2 követelmény). Ahhoz, hogy az oldalt elérjük, az alábbi két sort kell még elhelyeznünk az osztályban: @InjectPage("Mennyiseg") public abstract Mennyiseg getMennyiseg();
Ennek hatására a keretrendszer megadja az oldalnak a kért oldalra vonatkozó referenciát. Adatbevitel A Mennyiség megadása oldalon tehát be kell tudnunk kérni a mennyiséget (M2 követelmény). Ehhez a html template-ben az alábbiak szükségesek:
A value-ként megadott OGNL kifejezés a Mennyiseg osztály int mennyiseg attribútumát fogja generáláskor kiolvasni, az űrlap elküldése után pedig visszaírni. Validáció A Tapestry rendszer által adott validációs rendszerben az érvényesítést végző programkód önmagában áll, csupán az érvényesítendő adathoz fér hozzá (kényelmesen). Alapkoncepciója, hogy atomi validátorokból állítunk össze egy kritériumhalmazt a mezőkkel szemben. Az így megalkotott atomi validátorok széleskörűen újrahasznosíthatók. A validátorokat egy speciális nyelv segítségével a mező megadásakor deklarálhatjuk:
Itt két különálló validátor fog működésbe lépni (M3 követelmény). Ilyen atomi validátort magunk is könnyen készíthetünk, és a rendszerbe illeszthetünk. A mechanizmus hátránya azonban, hogy üzleti adatoktól függő validációs kritériumot nem (illetve nagyon nehezen) adhatunk (M4 követelmény), mert a validátor osztály nem fér hozzá a validált mezőt tartalmazó lap osztályához, így a szükséges üzleti adathoz. Ennek erőszakos megvalósítása megsérti a rendszer architektúráját. Az ilyen jellegű érvényesítésről tehát nekünk kell gondoskodnunk. Fel kell vennünk egy hibaüzenet változót (M5 követelmény):
41
@Persist public abstract void setErrorMessage(String errorMessage);
Majd ezt nekünk kell kezelnünk: public IPage doClick() { if (getLogic().vanRaktaron(getTermek(), getMennyiseg())) { setErrorMessage(""); List kosar = getKosar(); Tetel tetel = new Tetel(); tetel.setTermek(getTermek()); tetel.setMennyiseg(getMennyiseg()); kosar.add(tetel); Kosar kosarPage = getKosarPage(); return kosarPage; } else setErrorMessage("Ennyi termék nincs raktáron."); return null; }
Az alkalmazás állapotának megtartása A Tapestry rendszer tervezésekor alapvető szempont volt a hatékonyság. Egy nagy terheltségű rendszert általában több szerver szolgál ki. Egy session létrejöttekor az adott sessionhoz tartozó információk egy szerveren jönnek létre. Azonban a szerver meghibásodása esetén, vagy erőforrás-átszervezés miatt a kiszolgálást bármikor átveheti egy másik szerver, ezt a Tapestry rendszer teljesen átlátszó módon végzi. Ahhoz azonban, hogy ez hatékonyan működhessen, az oldalban és az alkalmazás egész folyamata során perzisztens információt speciálisan kell kezelnünk. Az oldal többszöri lekérése folyamán perzisztens információt a @Persist annotációval kell ellátnunk. Az egész alkalmazásra vonatkozó információt, ami például a felhasználó kosara (nálunk ez nem az üzleti modell része, az adatbázisba már csak a számla kerül), külön konfigurációs fájlban kell megadnunk. Hozzáférése az alábbi módon megoldott: @InjectState("kosar") public abstract List getKosar();
A kosárhoz validációkor megadott példa is így fér hozzá. A Tapestry rendszer nagyban megnyerte tetszésünket, a később kifejtett okokból azonban mégis saját keretrendszer fejlesztésébe kezdtünk. A Tapestry ilyen nagy mértékű strukturáltsága azonban lehetőséget biztosít arra, hogy az általunk megadott absztrakt modell esetleg Tapestry alapokon kerüljön implementációba. Előbb azonban nézzünk meg még egy fontos keretrendszer.
3.4.9. Millstone A Millstone egy finn cég, az IT Mill rendszere [20]. Keretrendszerével úgy fejleszthetünk webes alkalmazást, mintha javax.Swing-es alkalmazást fejlesztenénk. Hogy ezt érzékeltessük, nézzünk egy példát:
42
public class MyApplication extends Application { Label label; public void init() { Window mainWindow = new Window("main"); label = new Label("This is my application"); mainWindow.addComponent(label); Button button = new Button("This is my button"); button.addListener(new Button.ClickListener() { public void buttonClick(ClickEvent arg0) { label.setCaption("Button clicked"); } }); mainWindow.addComponent(button); setMainWindow(mainWindow); } }
Azzal, hogy az alkalmazásunkat mégis weben keresztül érik el, csupán annyiban kell szembesülnünk, hogy bizonyos változásokról (például egy listaelem kiválasztása) nem mindig érdemes azonnal tudomást szereznünk, tehát egy beviteli mező (tipikusan egy lenyíló lista) értékváltozásáról csak akkor kérjünk azonnali (immediate) értesítést, ha valóban azonnal szükségünk van rá, így megspórolunk egy oldal-újratöltést. Ettől eltekintve azonban az alkalmazásunk akár egy kliensoldali ablakos alkalmazás is lehetne. A visszahívási mechanizmust a javax.swing-ben megszokott Listener-ek valósítják meg. Ezek után úgy strukturáljuk az alkalmazásunk kódját, ahogyan nekünk tetszik. Lehet az alkalmazás egyetlen Java osztály, mi azonban minden egyes képernyőhöz egy-egy Windowból leszármazó osztályt deklaráltunk. A Millstone rendszer a létrehozott komponensekből XML kimenetet generál, ennek neve UIDL - User Interface Declaration Language. Ezen UIDL adatból XSL template-ek készítenek HTML kimenetet. A HTML részletei rejtve maradnak előlünk (részben, némi konfigurációs szabadságot hagyva), valamint a visszahívás nehézségeit is elrejti előlünk, mint említettük, nekünk csak egy Listenert kell rendelnünk az adott eseményhez.
Browser HTTP
Millstone DispatcherServlet HTML
XSLT
EventDispatcher
UIDL
Event
Listener
Window
Button
Label
Table
TabSheet
...
Application 10. ábra – A Millstone struktúrája
A rendszerben a HTTP kéréseket a Millstone DispatcherServlet-je végzi. Egy új bejelentkezéskor (új session keletkezésekor) alkalmazásunkból egy példány készül, majd a session további eseményeit már automatikusan a létrejött alkalmazáspéldány kapja meg. Ezen 43
példány nem látszólagos, mint azt a korábbi rendszereknél megszoktuk. A korábbi rendszerekben az alkalmazás állapotát akár a felhasználóhoz (a html oldalba) kikerülő információ, vagy a Servlet technológia által biztosított HttpSession tárolta. A Millstone esetében valóban egy alkalmazáspéldányunk fut a szerver Java VM-jén, eseményvezérelten. Ez természetesen igénytől függően előny vagy hátrány. Egy nagy terheltségű, nagyban elosztott futású rendszer esetén ez nem mindenképpen kívánatos. A fejlesztés során azonban igen nagy kényelmet jelent, és segít az alkalmazás implementációját is strukturáltan tartani. Adatmegjelenítés P1 követelményünk szerint tehát a Polc oldalon meg kell jelenítenünk a termékek listáját. Ehhez a Polc konstruktorában fel kell töltenünk egy táblázatot: table = new Table(); table.addContainerProperty("megnevezes", String.class, ""); table.addContainerProperty("ar", Double.class, 0); table.addContainerProperty("keszlet", Integer.class, 0); for (Termek termek : termekek) { table.addItem( new Object[] { termek.getMegnevezes(), termek.getAr(), termek.getKeszlet() }, termek); } table.setSelectable(true); addComponent(table);
Lehetőségünk van természetesen, szintén a Swing rendszerhez hasonlóan, saját Container példány táblázathoz rendeléséhez is, ez modulárisabb adat-hozzárendelést biztosít.
Visszahívás P2 követelmény alapján a választott termékhez tartozó Mennyiség megadása képernyőre kell továbblépnünk: button = new Button("Rendel"); button.dependsOn(table); button.addListener( new Button.ClickListener() { public void buttonClick(ClickEvent arg0) { if (table.getValue() == null) { errorLabel.setCaption("Kérem válasszon terméket"); } else { errorLabel.setCaption(""); getApp().mennyiseg.termek = (Termek)table.getValue(); getApp().setMainWindow(getApp().mennyiseg); } } }); addComponent(button);
Adatbevitel A mennyiség bevitele (M2 követelmény) egyszerűen egy TextField komponens felvételével megoldható. Az adathoz való későbbi hozzáférés egyszerűen a textField.getValue()
függvénnyel végezhető.
44
Validáció Mint ahogyan a Visszahívás kódrészletében is látszik, a Millstone rendszer önmagában nem ad támogatást a validációhoz kapcsolódó teendők megvalósítására, a hibajelző címkét tehát nekünk kell kezelnünk. Validátort ennek ellenére rendelhetünk az egyes mezőkhöz: public class MennyisegValidator implements Validator { public boolean isValid(Object object) { try { int value = Integer.parseInt((String)object); return (value > 0) &&(getLogic().vanRaktaron(termek, value)); } catch (NumberFormatException e) { return false; } } }
Ezen osztály implementáció már lehet lokális, tehát hozzáférünk a kérdéses termékhez is. Ennek köszönhetően a validáció eredménye függhet üzleti adattól is (van-e raktáron megfelelő mennyiség – M4 követelmény). A validálás eredményének kezelése azonban ránk van bízva: public void buttonClick(ClickEvent arg0) { if (mennyiseg.isValid()) { Termek termek = (Termek)getApp().polc.table.getValue(); int mennyisegInt = Integer.parseInt((String)mennyiseg.getValue()); getApp().addTetel(termek, mennyisegInt); getApp().setMainWindow(getApp().kosarWnd); message.setCaption(""); } else { message.setCaption("Érvénytelen mennyiség"); } }
3.4.10.
Saját keretrendszer koncepciója
Az előzőekben tárgyalt keretrendszerek tapasztalatainak fényében most röviden bemutatjuk a saját keretrendszerünk alapvető tulajdonságait. Motiváció Üzleti alkalmazások fejlesztésekor már igen kiforrott eszközök és módszerek állnak rendelkezésre az üzleti adatmodellezéshez, az üzleti logika specifikálásához és modellezéséhez. Az alkalmazás, amely ezen üzleti funkciókat csokorba fogva a felhasználó számára elérhetővé teszi, igen nehezen modellezhető. A modellezhetőségért ugyanis minden esetben fel kell adnunk az imperatív programozás nyújtotta teljes szabadságot, és bizonyos megkötő konvenciókkal kell élnünk, hogy az imperatív nyelvek modelljénél egyszerűbb és a tárgyhoz közelebb álló fogalmakból felépülő rendszerben dolgozhassunk. Az alkalmazás a Model View Controller architektúrában a Vezérlés és a Megjelenítés, míg a Modell az üzleti adatmodell és az üzleti logika. Az üzleti modellezésnél kiválóan tudunk munkafolyamat modelleket is alkalmazni, egy munkafolyamathoz azonban az alkalmazásnak is igazodnia kell. Célunk volt egy olyan modell megalkotása, amelyben össze tudjuk fogni a munkafolyamat bizonyos lépéseihez tartozó megjelenítendő adathalmazt, a végrehajtható teendőket, valamint e teendőket megvalósító üzleti funkciókat. Itt azonban már
45
vigyáznunk kellett, hogy az alkalmazott megkötéseket a végfelhasználó már ne tapasztalja kényelmetlenségként az alkalmazás használatakor. Úgy döntöttünk, hogy a megjelenítési réteget elkülönítjük a modellünktől, hiszen a munkafolyamatban nem releváns információ a megjelenítés módja, stílusa. Észrevételünk, hogy egy üzleti alkalmazásnak van egy rétege, amely már nem része az üzleti modellnek, azonban mégis független attól, hogy a szoftver végül egy HTML alapú webes alkalmazás lesz, egy asztali alkalmazás, vagy akár egy telefonos rendszer. A rendszert két részre bontjuk, egyrészt az alkalmazás formális modelljéről beszélünk, másrészt annak Javás implementációjáról. A rendszerhez fognak kapcsolódni továbbá a különféle megjelenítő rétegek. A Javás implementációt azért tárgyaljuk külön, mert a modelltől függetlenül sok lehetőség adódik a megvalósításra. A modell letisztulása után lehetőség van a metamodell és a hozzá tartozó modellezőeszköz megalkotására, majd az ebben elkészített modellből való kódgenerálásra, vagy szinkronizációra modell és kód között. Mivel rendszerünk kísérleti, fontos volt, hogy az egyes változtatásokat minimális idővel (turnaround time) tudjuk tesztelni, ezért élve a Java 5 annotációi által adott új nyelvi lehetőséggel, a rendszer modellje a kód maga lett. A rendszer azon része, ami még nem tisztázódott le, egyszerű imperatív Java programkódként szerepel az implementációban. A követelmények és a szemantika véglegesítése után azonban a legtöbb funkcióra annotációt vezettünk be, a leírást tehát deklaratívvá tettük. A modell A modellünk még nem teljesen kiforrott, teljesen formális specifikációt ezért most nem is adunk. A fejlesztés jelen fázisában már letisztult elemeket most szövegesen tárgyaljuk. Az alkalmazás nézeteket halmazát és egy kezdőnézetet definiál. Egy nézet megadja a megjelenítésre szánt adatok halmazát. Fontos konvenció, hogy a megjelenítő réteg nem kaphat üzleti entitást. Ha tehát meg akarjuk jeleníteni a termék nevét, nem adjuk kimenetre a termék entitást, csupán annak nevét. Ha a termékről minden információt meg akarunk jeleníteni, a termék példány akkor sem kerülhet kimenetre. Ennek két oka van, egyrészt, a megjelenítendő adat pusztán szöveges (primitív típusú). Másrészt, a megjelenítő rétegnek nem releváns információ például a termék (adatbázisbeli) azonosítója, és nem adhatunk lehetőséget a relációkon keresztül kapcsolt információkhoz való hozzáférésre sem (szamla.getAuthor().getAccountNo() tipikusan irreleváns, ha számlát jelenítjük meg). Az alkalmazásra illeszkedő megjelenítő réteggel szemben tehát teljes mértékben bizalmatlanok vagyunk. A megjelenítésre szánt adatokat primitív típusokként, és ezeket összefogó, faszerkezetbe rendeződő egységekként, adatelemként kezeljük. Egy egység minden esetben egyetlen üzleti logikai entitásból jön létre (termék, számla, tétel), annak megjelenítendő primitív adatait válogatja le, valamint az entitáshoz kapcsolódó csatolt entitásokból létrejött adathalmazokat definiálja (számla tételei). Egy nézetben definiálhatunk akciókat. Egy akció tartozhat az állapothoz vagy egy adatelemhez. Az akció tevékenysége és kimenetele a legkevésbé kiforrott a rendszerünkben. Általában egy üzleti logikai műveletet végez, majd lehetősége van az alkalmazást másik nézetbe vinni, vagy megmaradni az aktuális nézetben. A másik nézetnek tetszés szerint paramétereket adhat át. Lehetőség van az akciónak validátort megadni, amely a nézet betöltésekor fut le. Egy nem érvényesült akció nem fog létrejönni, például a termékek listájában, ha egy termékből már egyetlen darab sincs raktáron, nem fog létrejönni hozzá 46
rendelési lehetőség. Az akciót elláthatjuk különféle visszahívási paraméterekkel, mindezek természetesen nem kerülnek kimenetre, a rendszer maga tartja őket számon. Kimenetre csupán a létrejött akciók azonosítói kerülnek, így nem engedélyezett tevékenység meghívása semmilyen módon nem érhető el a megjelenítési oldalról. Egy nézetnek állapotváltozói lehetnek, ezek tipikusan üzleti adatok, mint például a bejelentkezett felhasználó vagy a megtekintés alatt álló számla. Beviteli mezők tartozhatnak az állapothoz vagy adatelemhez. A rendszerben a mezőkhöz validátorokat adhatunk meg. A validáció sikertelensége esetén a mezőhöz hibajelzés kapcsolható. Egyes mezőcsoportok sikeres validációja esetén a hozzá kapcsolódó feldolgozó-metódus fut le, mint akció. Bejelentkezve +Hallgato hallgato Mérés +Meres meres
Eredményeim Kijelentkezve
Mérés adatai
Feladat beadása
11. ábra – A nézeteink és egy állapotgép hasonlatossága.
A nézetek egymásból öröklődhetnek. A leszármazott állapot örökölhet kimeneti adathalmazt, akciókat, mezőket és állapotváltozókat. Egy üzleti alkalmazásban például gyakran a Kijelentkezve állapoton kívül a legtöbb további állapot a Bejelentkezve állapotból származik, amelynek öröklődő állapotváltozója a bejelentkezett felhasználó, és öröklődő akciója a kijelentkezés. Az alkalmazás nézetei mintegy állapotdiagram állapotai tartalmazzák egymást, akciói hasonlatosak az állapotdiagram átmeneteihez. Ennek szemléltetéséhez nem volt elégséges a webáruházas példánk, a példaalkalmazás fogalmaival dolgozó fiktív alkalmazás nézeteit felhasználva az alábbi ábrák szemléltetik a nézetek öröklődését. Állapot
Kijelentkezve
Bejelentkezve +Hallgato hallgato
Eredményeim
Mérés +Meres meres
Mérés adatai
Feladat beadása
12. ábra – A nézetek osztályainak öröklődése.
Az implementáció Mint a Tapestry rendszer tárgyalásakor említettük, jó lehetőség adódik az absztrakt modell Tapestry alkalmazásba való fordításához. Úgy döntöttünk azonban, hogy a modell implementációja is legyen teljesen absztrakt. Az alkalmazást tehát teljes egészében önmagában fut, és egy definiált interfészen keresztül érheti el a megjelenítő, amely lehet akár önmaga is absztrakt, vagy készülhet a konkrét alkalmazásra specifikusan. 47
Mindez lehetővé teszi, hogy ugyanazon alkalmazásra csatlakozzék webes megjelenítő, valamint távoli vagy helyi ablakos alkalmazás. A tesztelés során egy Swinges felhasználói felületen dolgozunk, amely megjeleníti a kimeneti adatokat XML-ben, továbbá lehetőséget ad a deklarált beviteli mezők kitöltésére, valamint az akciók meghívására.
13. ábra – A Swinges absztrakt megjelenítő.
Az alkalmazás absztrakt implementáció lehetővé teszi továbbá a tesztesetek könnyű implementációját, mivel nem szükséges egy bizonyos felhasználói interfészen keresztül tesztelünk, az alkalmazás primitívjeit közvetlenül hívhatjuk. Vizsgáljuk meg most az előző fejezetekkel összevetve a keretrendszer által nyújtott lehetőségeket. Adatmegjelenítés és visszahívás Vizsgáljuk meg a Polc nézetünk kódját. A nézetben deklarálni fogjuk, hogy a termékek listája kerül kimenetre. Megadjuk, hogy a termékek listája mely adatforrásból áll elő, és hogy egy termékről mely adatai kerülnek kimenetre. Deklarálunk egy "Kosár megtekintése" action-t, amely a Kosar nézetbe visz. Megadunk minden termékhez egy "Megrendelem" action-t, amely a mennyiség megadása nézetbe visz, kiválasztva az adott terméket. public class Polc extends BoltState { @DefaultOutput public Vector termekek; @Override public void load() { super.load(); termekek = generateActiveDataVector(TermekInfo.class, getLogic().getTermekek()); } @Action public void visitKosar() { app.enter(Kosar.class); }
48
public class TermekInfo extends ActiveData { @ModelObjectField public String megnevezes; @ModelObjectField public double ar; @ModelObjectField public int keszlet; public TermekInfo(Termek termek) { super(termek); }
}
}
@Action public void order() { app.setVariable("termek", modelObject); app.enter(Mennyiseg.class); }
A fenti kód tehát a Polc nézethez tartozó Java osztály. Mint láthatjuk, igen kevés imperatív elemet tartalmaz. A "@"-cal jelölt elemek a 3.1.3. pontban már említett annotációk. A DefaultOutput annotácó jelöli, hogy a termekek mező kimenetre fog kerülni. A load metódus kódja adja meg – kis mértékben imperatívan – hogy a termékek mely adatforrásból (getLogic().getTermekek()) kerül generálásra. A visitKosar() metódushoz az Action annotáció hatására készül action. A TermekInfo osztály definiálja, hogy egy termékhez milyen kimeneti adatok kapcsolódnak. A megnevezes, ar és keszlet mezők egy az egyben kimásolódnak az adott termék adataiból. Minden termékhez kapcsolódni fog az order() függvényt visszahívó action, amely a Mennyiseg nézet be visz. Mint látjuk, a fenti Java kód alig tartalmaz többet, mint a nézettel szemben támasztott szöveges követelmény. Adatbevitel A Mennyiség megadása állapotban bekérjük tehát, hogy a választott termékből hány darabot szeretne rendelni a felhasználó. Az adatbevitelhez egész egyszerűen az állapot vagy adatelem megfelelő mezőjét @StateField annotációval kell ellátnunk. Visszahíváskor a keretrendszer feltölti a felhasználói felületen kitöltött mező értékével a megfelelő attribútumot, így a render() metódus egyszerűen hozzáfér a megadott mennyiséghez. public class Mennyiseg extends BoltState { [...] @StateField public String mennyiseg;
A validáció a rendszerben egyelőre nem letisztult, lehetőségeit a következő fejezetben mutatjuk be.
49
3.5. A saját vezérlés bemutatása E fejezetben bemutatjuk, hogy a megvalósított saját keretrendszer segítségével hogyan fejleszthetünk absztrakt üzleti alkalmazásokat, majd hogyan építhetjük össze az üzleti és a megjelenítési réteggel.
3.5.1. Programozás a keretrendszerben Mint már említettük, a rendszer Java SE 5 környezetben került megvalósításra. Az annotációk segítségével a már letisztult modellel rendelkező részek deklaratívan, míg a kiforratlan megoldások imperatívan szerepelnek ugyanazon kódban. Az alkalmazásunk programozása tehát Java osztályok készítéséből áll. Az alkalmazást az absztrakt Application osztályból származtatjuk. Az Application végzi a nézetek működtetését, valamint kiterjesztési pontokat ad az üzleti logikához, és a megjelenítéshez való integrációhoz. A leszármazott alkalmazás feladata továbbá, hogy betöltse a kezdőnézetet. Nézetek A továbbiakban a nézeteket kell megírnunk. A nézeteinket a szintén egy absztrakt ősosztályból kell származtatnunk. Egy nézethez tartoznak tehát annak kimeneti adatai, az érvényes akciók és a beviteli mezők. Egy nézetet a rendszerben a Java osztálya azonosít (SajatNezet.class). A nézetnek definiáltunk bizonyos primitíveket, melyek az életciklusa során hívódnak. A nézetbe, mint állapotba való belépéskor az enter primitívje hívódik, elhagyásakor leave. Amikor az alkalmazás eseményt kap, de az nem jár állapotváltozással, load primitívje kerül meghívásra. A kimenet előállítását a render primitív végzi. Bár a fejlesztés abba az irányba halad, hogy komponens bázisú megjelenítést alkalmazzunk, ahol egy nézet az megjelenített adatokat mint egy konkrét ablakos kliens felhasználói felületét kezeli, és azok eseményeire reagál, jelen modellünk még inkább a webes alkalmazásokéhoz hasonlatos. Ezen változtatást konkrét igény esetén végezzük el, amennyiben valamely megjelenítési technológia (például AJAX) alkalmazását ez valóban elősegíti. Ebből adódik a modellünknek egy alapvetően fontos tulajdonsága. Ez annak a behatása, hogy végülis webes alkalmazást fejlesztünk. Egy átlagos webes alkalmazásban egy felhasználói beavatkozást, tehát HTTP kérést a HTML válaszként való visszaadása követ. A modellünkben egy akciót követően a kimeneti adatelemek teljesen újragenerálódnak, beleértve minden adatelemet, annak minden akcióját és mezőjét is. Egy interakció során tehát meghívódik a megfelelő metódus, amely általában egy, az előző render-kor előállított adatelemhez tartozik, amely elvégzi a megfelelő üzleti teendőket. Ezután viszont az adatelem megszűnik, a load hatására minden kimeneti objektum törlődik és újragenerálódik. Ez az üzleti adatok változásaira való reagálás miatt is kívánatos, mivel ezen adatelemek eredetileg az üzleti adatok csoportosított reprezentációi, és nem vezérlők. A vezérlési metódusok csupán kényelmi okok miatt szerepelnek az adatelemben. Adatok kimenetre adása Egy nézet kimenete tehát alapvetően primitív adatelemekből tevődik össze. A nézet kimeneti adatait egy Vector gyűjti. Bár a render primitív felelős ezen Vector felöltéséért, a 50
render() metódus leszármaztatására már nem adunk lehetőséget, a render metódust teljes
egészében a keretrendszer kezeli az annotációkban megadott információk alapján. Imperatív módú kimenet előállításra ugyan hagytunk egy kis lehetőséget, valójában száműztük a rendszerből. A kimenet megadását a nézet osztályában a DefaultOutput annotáció vezérli. Az ezzel jelölt publikus mező a render híváskor kimenetre kerül. Amennyiben a változó nem konstans, a load hívásban kell gondoskodnunk feltöltéséről. Az annotáció source paraméterében azonban megadhatunk egy üzleti entitást tartalmazó nézetváltozót, amelyből a megfelelő adatelem fog létrejönni (lásd később). Lehetőség van továbbá egy thisStateOnly jelző megadására, amely hatására a leszármazott nézetekben ez már nem fog kimenetre kerülni, és az adatelem sem jön létre hozzá. Legyen például egy Teszt nézetünk, melyben egy "Hello world!" üzenet és egy termékről készült adathalmaz kerül kimenetre. public class TestView extends ApplicationState { @StateVariable public Termek termek;
}
@DefaultOutput public String message = "Hello world!"; @DefaultOutput(source="termek") public TermekInfo termekInfo;
Adatelemek A rendszernek kimenete a korábbi elveink szerint bármilyen, primitív adatokat csoportosító Java objektum lehet, szigorúan tilos viszont üzleti entitást kimenetre helyezni. Az üzleti adatokat fedő, megjelenítésre kerülő osztályok adatfeltöltését a keretrendszer nagyban támogatja. Ehhez azonban egy új ősosztály bevezetésére volt szükségünk. Ezen kimeneti adatelemek minden esetben egyetlen üzleti entitásból jönnek létre, annak adatait fedik, tükrözik. Igény merült fel arra is, hogy az adott entitáshoz kapcsolódó teendőket az adatelemben, tehát a releváns adatok mellett programozzuk le. Az előbbi fejezetben is említett példa: public class TermekInfo extends ActiveData { @ModelObjectField public String megnevezes; @ModelObjectField public double ar; @ModelObjectField public int keszlet; public TermekInfo(Termek termek) { super(termek); }
}
@Action public void order() { app.setVariable("termek", modelObject); app.enter(Mennyiseg.class); }
Ezen adatelemből a következő XML kimenet készül:
51
Adatelemek automatizmusai A ModelObjectField-nek annotált publikus mezőket a rendszer automatikusan feltölti a forrásként megadott termék adataiból, név alapján megkeresve a mezőt. Szükség esetén az annotációnak paraméterként megadhatjuk a forrásul szolgáló mező nevét. További lehetőséget ad a ModelObjectCollection annotáció. Például a számlát fedő adatelem kódjában: @ModelObjectCollection(TetelInfo.class) public Vector tetelek;
Itt a Szamla.getTetelek() metódus segítségével lekért tételek mindegyikéhez létrejön egy TetelInfo fedő objektum. Bizonyos esetben a listában szereplő elemek mindegyike más osztályú objektum, így mással is szeretnénk fedni. Például az állatok listájában a kutyákat a KutyaInfo, a macskákat a MacskaInfo példányával fedjük. Ekkor nem adunk meg fedő osztályt az annotációban. @ModelObjectCollection public Vector allatok;
A rendszernek viszont meg kell tudni találnia a megfelelő fedő objektumot. Az ehhez szükséges információt Wraps annotáció deklarálja: @Wraps(Macska.class) public class MacskaInfo extends ActiveData<Macska> [...] @Wraps(Kutya.class) public class KutyaInfo extends ActiveData [...]
Az adatelemeink osztályai minden esetben az adott nézet belső osztályai, bár ezen megkötés még nem végleges. Egy nézet természetesen használhatja az ősnézeteinek fedő osztályait. A @Wraps annotáció kezelésekor a legmagasabb szinten megadott fedő osztály kerül majd használatra. Akciók Akciónak deklarálhatjuk a nézet és egy adatelem bármely paraméter nélküli publikus metódusát. Például public class Bejelentkezve extends ApplicationState { @Action public void logout() { [...] } }
Ezen annotáció alapján automatikusan regisztrálásra kerül az akció. Egy akció lehet azonban ennél általánosabb is. Egészen konkrétan egy akcióhoz tartozik annak neve (egyedi azonosítója), egy metódus (java.lang.reflect.Method), a meghívásánál megadandó paraméterek, valamint a metódus tulajdonosa. Egy további információ az akcióról, hogy dinamikus-e. A statikus akciók csak egyszer kerülnek elkészítésre, és a továbbiakban nem változnak, ilyen tipikusan a fenti példa logout akciója. A dinamikus akciók újratöltéskor (load) törlődnek, ekkor újra kell őket generálni. Ezen akciók
52
(a fenti termék order akciója) adatelemekhez kapcsolódnak, és mivel az adatelemek változhatnak újratöltések között, a hozzájuk rendelt akciókat is újra el kell készítenünk. Amennyiben elégséges az Action annotáció által nyújtott lehetőség, mindezzel nem kell foglalkoznunk. Ellenkező esetben azonban a dinamikus akciókat a load primitívben nekünk kell létrehoznunk. Mezők Beviteli mező a nézet vagy egy adateleme egy publikus mezőjéből készül a StateField annotáció hatására. A mező bármilyen primitív típussal rendelkezhet, megjelenítő rendszereink azonban jelenleg kizárólag szöveges értékű adatokat kezelnek. A keretrendszer kiegészítését valószínűleg csak a fájlfeltöltés hatékony kezelése fogja igényelni. Az annotációnak paraméterként megadhatjuk, ha nem szeretnénk, hogy a mező értéke az újratöltések során megmaradjon. Tipikusan ilyen mező a jelszó. A mezőről a kimenetre a FieldInfo szerinti információk kerülnek automatikusan. Ezek tartalmazzák a mező típusát, a szerkesztésére vonatkozó kiegészítő információkat, valamint a mező aktuális értékét. Validáció A validációval kapcsolatos igényeinket az alábbi modell elégíti ki, implementációja jelenleg nem végleges. Az űrlap elküldését nyilván egy akció fogadja. Azt szeretnénk, ha ezen akció csak akkor hívódna meg, ha a mezőkre megadott validációs kritériumok már teljesültek. Egy mezőre, természetesen annotációval, adhatunk majd meg különféle kritériumokat. Az egyszerű, lokális döntést igénylő kritériumok külön osztályokban kerülnek megírásra. Lehetőség lesz a nézetben vagy az adatelemben lokálisan implementált validátor megadására, amely már üzleti adatokhoz is hozzáférhet a kritérium vizsgálatakor. Ha bármely mező validációja meghiúsul, ez okozhatja a teljes beviteli eljárás elvetését, de átlagos esetben egy hibaüzenet fogunk rendelni a mezőről kiadott információhoz. Ennek segítségével a megjelenítő a mező mellett jelezheti a hibát (például a jelszó legyen hosszabb 6 karakternél). Változók A nézeteinkhez tartozhatnak változók. A jelen implementációból adódóan természetesen a nézet osztályának bármilyen mezője megmarad az események között, ez azonban nem része a modellnek, így kerülendő. Az alkalmazás egyik nézetének forrásinformációja általában néhány üzleti entitás (Termék, Tétel, Számla), esetleg kiegészítő primitív típusú adatok (például lapozott lista esetén az aktuális lapszám). Ezeket a nézetek változóinak tekintjük, az implementációban StateVariable annotációval kell őket ellátnunk. Egy webes megvalósításával szemben gyakran követelményünk, hogy a böngészőnkben könyvjelzőket helyezhessünk el az oldalról. Ha könyvjelzőt helyezünk el például egy adott fórum adott hozzászólásánál, akkor a böngésző az oldalhoz tartozó URL-t fogja eltárolni. Később, a könyvjelző felütésekor a böngésző az eltárolt URL-t fogja megnyitni. Alkalmazásunknak ekkor fel kell hoznia egy esetleges bejelentkező képernyőt, majd sikeres autentikáció után a megfelelő oldalt elő kell hoznia. Szükségünk lesz tehát ilyen URL-ek feldolgozására. A nézet változóit fogjuk letárolni az URL paramétereiként. Az üzleti entitásoknak az egyedi azonosítójukat, a primitív típusú elemeket sorosítva tárolhatjuk. Fontos mindezért, hogy a nézet a működéséhez minden 53
szükséges információt a deklarált változóiban tároljon. Speciálisan kezelendő viszont a bejelentkezett felhasználót tároló változó, mivel ezt nem URL-ből fogjuk venni, hanem a bejelentkezéssel. Szükség lesz továbbá a paraméterként kapott adatok validációja is. Elterjedt megoldás ezenkívül a weboldalon elhelyezett "könyvjelző elhelyezése" funkció, amely igény esetén készít állapotot reprodukáló URL-t. Ez esetben lehetőségünk van a szükséges információ szerveroldali kezelésére is. Öröklődés Mivel a nézeteink öröklődése szemantikailag nagyban hasonlít a Java osztályok között vett öröklődésre, így a modellben a leszármazott nézet az implementációban is az ősnézet osztályának leszármazott osztálya lesz. Az osztály szemantikája azonban nem teljesen felel meg az igényeinknek. Egy alkalmazáson belül ugyanis egy nézetből egy darab létezik, ebből a szempontból tehát inkább objektumokat hozunk létre. Több alkalmazás között vett azonos nézetek természetesen egy osztályba tartoznak. Ez a különbség miatt egy fontos teendője van a keretrendszernek. A leszármazott nézetek ugyanis praktikusan öröklik az alkalmazáson belül az ősnézet változóinak és mezőinek aktuális értékeit is. Megvan az a szerencsénk azonban, hogy egy alkalmazásban egyszerre csak egy nézet aktív, a nézetpéldányok közötti változó-szinkronizáció könnyen megvalósítható feladat. Nézetváltáskor tehát a régi nézet és az új nézet közös változóit át kell mentenünk a régi nézetből az új nézetbe. Hasonlóan kell eljárnunk a megmaradó értékű mezőkkel is.
3.5.2. Kapcsolódás az üzleti logikához A keretrendszer nem definiálja az üzleti réteghez való csatlakozást, célunk ugyanis, hogy ugyanúgy használható legyen statikus üzleti réteggel rendelkező alkalmazásban vagy helyi perzisztenciával megvalósított architektúrában, mint elosztott üzleti logikájú rendszerben, mint amilyet a tárgyalt sémánk megad. Az első két esetben a legcélravezetőbb, ha a nézet alapjául szolgáló ApplicationState osztályból származtatunk egy saját alaposztályt, amely rendelkezik az üzleti logika objektumaival. Ezután minden nézetünkben hozzáférhetünk az üzleti funkcióinkhoz. Az üzleti logikát azonban az alkalmazás hozza létre, így az alkalmazásnak át kell adnia a nézeteinek a megfelelő objektumokat. Erre szolgál az alkalmazás osztályában kiterjeszthető initiateCurrentState() függvény. Üzleti adatok frissítése A harmadik eset kezelése még nem teljesen kiforrott. Ezen esetben a nézet minden letöltésekor JNDI segítségével kapcsolódik a távoli üzleti logikai elemhez (Session Bean). Minden letöltés (load) alkalmával szükség van a felhasznált üzleti entitások frissítéséhez, könnyen lehet ugyanis, hogy az üzleti elem, amint dolgozunk, időközben például törlésre került. E feladat elvégzésének lett helye a beforeLoad primitív, mivel letöltéskor már rendelkezésre kell, hogy álljanak a kimenetek forrásaiként szolgáló üzleti entitások. Szeretnénk azonban ezen kiegészítéseket is lehetőség szerint deklaratív keretek között tartani. Mivel szerettük volna az üzleti alkalmazás keretrendszerét függetlenül tartani a Java EE-től, ezt nem tehettük magában a keretrendszerben. Ezért ahhoz a megoldáshoz fordultunk, hogy kiegészítési pontokat hoztunk létre a modellen, amelyek segítségével deklaratív keretek között tartva adhatunk új szemantikájú (implementációjú) elemeket a rendszerbe. 54
A fenti problémát úgy oldottuk meg tehát, hogy létrehoztunk egy olyan annotációt, amely a betöltéskor frissítendő, üzleti entitást tároló nézetváltozókat jelöli meg. Az annotáció deklarációja megadja, hogy mely osztály végzi el tényleges frissítést. A keretrendszer a nézet betöltésekor az így annotált mezőkre meghívja a megadott frissítőt. A szükséges kiegészítést ily módon deklaratív keretek között tudtuk megoldani, ezzel elősegítve az alkalmazás minél szélesebb körű modellezését, mivel a modellben az adott mezőt „frissítendő” tulajdonsága a programkódban egy egyszerű annotációnak felel meg. Validáció A fent vázolt validációs rendszert szeretnénk összekötni a Hibernate rendszer által biztosított perzisztenciaszintű validációval. A Hibernate validátorok az üzleti modellben kerülnek deklarációra. Kezelésük a lokális döntést igénylő validátorokéhoz hasonlatos.
3.5.3. Kapcsolódás a megjelenítéshez Egy nézet kimenete eredetileg egyszerű java objektumok halmaza, jelen megvalósításban a kimenet azonban kizárólag XML formátumban érhető el a megjelenítést végző réteg számára. Ezen később változtatni fogunk, most azonban nagyban segítette a fejlesztést ez a konvenció. Az XML-be való sorosítást egy külön erre a célra fejlesztett speciális sorosító végzi. Az alkalmazás írásakor tehát pusztán a kimeneti Java osztályokat kell megalkotnunk. A sorosítás során az a mezőnek és akciónak deklarált elemekhez automatikusan generálódnak a megfelelő leíró részletek. Megjelenítők A rendszerre a megadott interfészen keresztül gyakorlatilag bármilyen megjelenítő kapcsolódhat. A tesztelés során a fent említett Swing-es alkalmazást használjuk. A webes megjelenítést egy egyszerű HttpServlet végezheti, amely a kimeneti XML adathoz hozzákapcsolja a megfelelő XSLT transzformációt. A visszahívás kezelésével a megjelenítő rétegnek foglalkoznia kell. Teendője, hogy a felhasználó által választott akció azonosítójával meghívja az alkalmazás invoke(String id) metódusát, illetve a felhasználó által kitöltött mezőket a setFields(Map<String, Object> fields) metódus segítségével visszaadja a rendszernek. A különféle típusok (szöveg, dátum, fájl) közötti konverzió a megjelenítő feladata. Az alkalmazás absztrakt interfésze Az alkalmazásmodell bármely felhasználásában elveink szerint a megjelenítés az alább interfészen keresztül éri el alkalmazásunkat: public interface Application { public Vector<String> getActions(); public Map<String, FieldInfo> getFields(); public void setFields(Map<String, Object> fieldValues); public void invokeAction(String actionId); public String render(); public String getCurrentStateId(); }
Ezen egyszerű hívások adnak meg minden információt a megjelenítő rétegnek. A megjelenítés felépítéséhez szükséges információt az adatkimenet maga (amely jelenleg XML-be sorosítva érhető el), a hívható akciók listája, a szerkeszthető beviteli mezők listája, és az aktuális nézet kódja. Visszahíváshoz a mezők beállítása és az akció meghívása szükséges. 55
A megjelenítők teendői Egy webes megjelenítő feladata tehát az XML-es forrásból a HTML kimenet előállítása. Ezen HTML-ben a visszahívást megadó URL-ben maga konvenciói szerint kell, hogy megadja a szükséges adatokat. Tipikusan a meghívott akció azonosítóját kell URL-ben megadnunk. A megjelenítőnek kell arról gondoskodnia továbbá, hogy a legenerált beviteli mezők értékeit az HTML űrlap elküldése után értelmezze, és a fenti interfészen az alkalmazásnak visszaadja.
56
3.6. A megjelenítés megvalósítása Ebben a fejezetben a megjelenítés témakörével foglalkozunk. Röviden írunk az általános alkalmazások felhasználói felületeiről, a webes felhasználói felületekről és a jelenlegi fejlesztési irányokról. Majd vázlatosan bemutatunk egy fejlesztési esettanulmányt, ami alapján a megjelenítési rétegünk koncepcióját kialakítottuk. A koncepciónk elméleti előnyeinek felsorolása mellett bemutatjuk a megvalósíthatóság korlátait, és elemezzük, hogy a helyzet hogyan fog a közeljövőben – reményeink szerint pozitív irányba – változni.
3.6.1. Általánosságban A megjelenítési réteg a felhasználói felületet állítja elő, ezzel áll közvetlen kapcsolatban a felhasználó. Ennek a rétegnek általánosságban nagyon sokfajta feltételnek kell megfelelnie, melyek sokszor egymással ellentétesek. Az informatikai alkalmazások többségében rögzített, hogy billentyűzet és egér a beviteli eszközök, és egy képernyő a kimenet3. Ezeket az eszközöket felhasználva a tipikus feladatokra vezérlők (widgetek) alakultak ki: szövegbeviteli mező, gomb, választó kapcsoló, stb. Ez a fejlesztést is gyorsítja, és a felhasználók is könnyebben megtanulják az alkalmazás használatát: A vezérlők az interakció egy magasabb, absztraktabb szintjét jelentik. Ezért alakultak ki az ún. vizuális programozási nyelvek, melyekben fejlesztő először a vezérlőket helyezi el vizuálisan, majd azokat köti az adatokhoz és műveletekhez. Aktív kutatások folynak4, amelyek célja, hogy a felhasználói felületet még absztraktabb módon fogalmazhassa meg a fejlesztő, vagy hogy a megszokott vezérlők köréből kitörve, újszerű interakciót valósítsanak meg. Jellemző, hogy az elméleti eredmények csak egy-egy példaprojectben manifesztálódnak.
3.6.2. Absztrakt felhasználói felület definíciók A felhasználói felület tervezését továbbfejleszthetjük, ha még absztraktabb szinten fogalmazzuk meg az elvárásainkat. A megvalósított és elméletben létező megoldások az absztrakciós szintek széles skáláján mozognak. A legszélsőségesebb esetek egy kiterjesztett UML, vagy hasonló célú modellben elhelyezett információk alapján generálják le a teljes vezérlési-megjelenítési réteget. Ezek gyakorlati használhatósága jelenleg még kicsi. Gyakorlatban működő megoldások a vezérlők egy kicsit absztraktabb, lényegretörőbb megfogalmazását teszik lehetővé. A motiváció itt az emberi kódolás csökkentése, a hordozhatóság, illetve a vezérlési logika és a design szétválasztása [32]. Két gyakorlatban használt rendszert hozunk fel példaként: a Mozilla Firefox böngészőhoz kifejlesztett XUL-t, és a Microsoft Windows Vista operációs rendszerhez kifejlesztett XAML-t. Mindkettőben közös, hogy megjelenést és az alkalmazás többi rétegeit minél élesebben szétválasztják, és XML-t használnak interfésznek [29]. Így a különböző rétegek fejlesztése függetlenebbül történhet, ami a fejlesztési projektet gyorsabbá teszi, és a hibák terjedését akadályozza. A megjelenítési réteg ilyenfajta elválasztásának ötletét a saját fejlesztéseink során is felhasználtuk már.
3
A beágyazott rendszerek és a fogyatékkal élők számára készült alkalmazások például kivételt képeznek. Ugyancsak nem felelnek meg ennek a megközelítésnek a mobiltelefonokon futó alkalmazások. 4
Egy kutatási projekt a felhasználói felületek modellezésével kapcsolatban: [37], [36]. Az újfajta vezérlőkkel kapcsolatban a [31] címen elérhető bemutató oldal figyelemreméltó.
57
3.6.3. Esettanulmány A Számítógép Laboratórium IV. tantárgy célkitűzése: Objektum orientált alkalmazás készítése UML (Unified Modeling Language) leírással, Javaban megvalósítva, RUP (Rational Unified Process) processz szerint. A hallgatók 3–4 fős csoportokban dolgoznak és készítik el a dokumentumokat és az alkalmazást a megadott ütemezés szerint. A JavaNyulak csoport tagja volt jelen dolgozat egyik szerzője. A csoport fejlesztési irányvonala élesen különbözött a többi csoportétól. Az új ötlet a tesztelést szolgáló prototípus készítése közben merült fel: a teljes modell rétegről rekurzív bejárással egy állapotképet készítettünk, melynek eredménye egy XML dokumentum volt, majd egy-egy XSLT transzformáció „igen” vagy „nem” eredménye mondta meg, hogy az adott teszteset sikeres-e. A tesztelést szolgáló kód egyszerű volt, minden tesztesethez azonos, és a végső kódban is benne maradhatott. Ezzel a megközelítéssel a tesztelés fázisában keletkező új hibák esélye minimálisra csökkent. Ám a megoldás nem csak a tesztelést oldotta meg, hanem az állapotmentést és visszatöltést is. Ezen túl, a grafikus felület kialakítására is egy egyszerű lehetőség kínálkozott: egyszerűen ezt a XML dokumentumban található információt kell megjeleníteni a felhasználó számára. Próbaképp egy HTML-lé konvertáló XSLT-t futtatunk, aminek eredményét egy kész HTML megjelenítő komponenssel jelenítettünk meg. A megoldás sajnos nem volt elfogadható, mert az alkalmazás egy játék elkészítése volt, és a felhasznált komponens túl lassú volt a folytonos mozgás megjelenítéséhez. Ha nem folytonos megjelenítést igénylő alkalmazást fejlesztettünk volna, akkor a munka 30%-át spóroltuk volna meg ezzel megoldással.
3.6.4. Webes felhasználói felület A webet – mely alatt most a http protokollt és a html és kapcsolódó formátumokat értjük – elsősorban egyirányú kommunikációra fejlesztették ki: sokféle formátumú média egységes megjelenítésére. Erre tanúbizonyság, hogy csak a HTML 2.0 verziójában (1995) jelent meg a FORM elem, amely lehetővé tette az általános vissza irányú kommunikációt. A FORM-ba ágyazható INPUT elemmel az általánosan elterjedt vezérlőket tudjuk a weboldalba illeszteni. A felhasználó által bevitt adatok mindmáig szövegesen, mezőnév=érték párokként jutnak el a kiszolgálóhoz a http protokollon keresztül, minden adatmódosításhoz, eseményhez egy http kérés szükséges. Az adatokat, utasításokat a kiszolgáló oldalon vissza kell fejteni a szöveges formából, elvégezni a megfelelő műveleteket, majd új tartalmat – legtöbbször egy teljes weboldalt – küldeni válaszképp [26][25]. Az adatfeldolgozás egy része átcsoportosítható a kliens oldalra, a HTML-be ágyazott szkriptek segítségével. Ennek szélsőséges esete az AJAX, ahol az oldalon futó javascript kód állítja elő teljes egészében felületet, és háttérben, speciális http kérésekkel kommunikál a kiszolgálóval. Ezekben a kérésekben az adatok szöveges reprezentációját egy-egy XML dokumentum biztosítja, és a válasz is XML dokumentum, ami alapján a kliensoldali szkript dinamikusan módosítja a weblapot. Így a felhasználó sosem látja az oldal újratöltődését, folytonos interakciót érzékel, de a kommunikáció valójában még mindig szakaszos. Azoknál az alkalmazásoknál, ahol a felhasználók sok adatot visznek be, komoly fejlesztői erőforrásokat igényel a vezérlők kialakítása és adatkötése. Egyfelől a vezérlőket megjelenítő HTML kódba bele kell fogalmazni a lehetséges értékeket, másfelől az adatokat visszaküldő http kérés adatmezői alapján a kiszolgálónak el kell döntenie, hogy melyik kliens küldte, és milyen művelet kell végrehajtani a hatására. Ha a gyorsabb reakció érdekében kliensoldali értéktartomány vagy egyéb kényszer-ellenőrzést kívánunk végrehajtani, akkor figyelembe kell venni, hogy a különböző böngészőkben némiképp eltérően viselkedhet ugyanaz a szkriptkód. 58
Ezért számos keretrendszer létezik, melyek megpróbálják ezeket a problémákat elfedni. Ezeknél, a vizuális nyelvek mintájára, absztrakt vezérlőkből állíthatjuk össze a weblapjainkat, és az adott programozási környezetbe illeszkedő eszközzel tudjuk az adatokat és eseményeket az alkalmazás többi részéhez kötni. A http kéréseket feldolgozó és a válaszokat előállító kódot a keretrendszer generálja. Ezek a keretrendszerek viszont értelemszerűen nem tudják kihasználni a webben rejlő gazdag adatreprezentációs lehetőségeket. Az általunk vizsgált, üzleti folyamatokat támogató alkalmazásoknál jellemzőbb, hogy az alkalmazás a felhasználónak adatok jelenít meg, változatos nézetekben. A vissza-irányú kommunikáció kevésbé jellemző, legtöbbször jóváhagyó jellegű, melynek tipikus formája egy linkre való kattintás. A felhasználók igényeinek minél jobban megfelelő megjelenítés érdekében érdemes a HTML adta lehetőségeket kihasználni, és nem szűk vezérlőkészletet alkalmazni. Ekkor viszont nehézséget okoz, hogy a dinamikusan generált információt egy statikus elemekből felépülő HTML keret különböző pontjaira kell beágyazni. Erre két tipikus megközelítés a html oldalba ágyazott szerveroldali szkript (pl.: php) és a forráskódba ágyazott html kódrészetek (pl.: CGI, Servlet). Véleményünk szerint mindkettő átláthatatlansághoz és hibalehetőségekhez vezet. Az általunk javasolt koncepció szerint az alkalmazásnak a releváns adatokat egy (XML) adatszerkezetben kell előállítania, és azt egy (XSL) transzformációnak kell HTML oldallá alakítania. Az 1999-ben kiadott XSLT 1.0 ajánlás [27] célja egy XML-ből XML-be képző transzformációs nyelv leírása, ahol különös hangsúlyt kapott a HTML-lé alakítás. Ezt az ajánlást manapság az elterjedt böngészők mind betartják, így ha egy olyan XML dokumentumot nyitunk meg velük, amely egy XSLT-re hivatkozik, akkor azt automatikusan lefuttatják, és az eredményül kapott HMTL dokumentumot jelenítik meg.
3.6.5. A megjelenítés a megvalósítási sémánkban A koncepciónk az, hogy az állapotokon alapuló vezérlési rétegünk minden állapotban egy XML dokumentumba gyűjti ki a felhasználó számára releváns információkat, majd ezt elküldi a felhasználó böngészőjének, megjelölve egy XSLT-t, amely alapján a böngésző megjeleníti az adatokat. A felhasználónak lehetősége van állapotonként több XSLT-ből választani, akár sajátot is megadhat. Ennek első nyilvánvaló előnye, hogy a felhasználó megjelenítési rétegre vonatkozó új igényei szinte soha nem érintik a vezérlési réteget. Csak akkor van szükségünk módosításra, ha a releváns adatok köre módosul, ez pedig tipikusan a modell réteg módosítását is igényli – ez viszont egyébként is mindenképpen érinti a vezérlési réteget. Az architektúrából adódóan a felhasználó a nyers, feldolgozható adatokhoz fér hozzá, úgy, hogy ez a biztonságot nem veszélyezteti. Az alkalmazást integrálhatjuk más rendszerekkel, anélkül, hogy az alsóbb rétegeket módosítanunk kellene. Ilyen lehet például egy automatikus kimutatás készítése. Lehetséges, hogy a felhasználók továbbfejlesszék a felületet, anélkül, hogy az alkalmazás többi rétegéhez hozzáférjenek fejlesztői vagy adminisztrátori szinten.5
5
Az példaprojektünknél reális elképzelés, hogy a felhasználó szakemberek átírják a felületet a nagyobb felhasználói élmény érdekében. A Neptun.net portál BME-es bevezetése után alig egy hónappal megjelent egy Firefox plugin, mely a portál bizonyos listáiban átállítja az alapértelmezetten kijelölt elemet, illetve néhány összefoglaló táblázathoz letölt és megjelenít egy plusz oszlopnyi információt.
59
A választható XSL transzformációval a CSS technológiánál nagyságrendekkel szabadabban módosítható a kinézet: nem csak a színeket, formákat és elhelyezést változtathatjuk kötetlenül, hanem az egymásba ágyazódást, a kapcsolatokat, sorrendezést, egyszóval a logikát is. A megoldási sémából adódik, hogy a megjelenítési réteg fejlesztése XSL transzformációk írásából áll. Ez a deklaratív programozási paradigmába sorolható, mely sokfajta probléma esetén tömörebb, hatékonyabb kódolást tesz lehetővé. Erre példa az adatok (pl.: dátumok) azonos formázásának egyszerűsége megfelelő template-ek létrehozásával. Az XSLT for-each eleme kiválóan alkalmas leválogatásokra, sorrendezésekre és szűrésekre, ezzel a megjelenítés logikája könnyen szervezhető, illetve néhány erőforrás-igényesebb művelet áthelyezhető a kliens számítógépére. Az XPath kifejezések segítségével egy-egy információt könnyedén megcímezhetünk, és fel tudunk használni az eredmény weblap különböző területein. Az XSL dokumentumok egymásba ágyazásának lehetőségével a logikailag összetartozó template-eket különböző fájlokba gyűjthetjük, mely javítja az átláthatóságot. Az előbb felsorolt lehetőségekkel nagyon látványos példa weblapokat lehet készíteni, azonban meg kell jegyeznünk, hogy a gyakorlati problémák megoldásakor könnyen az XSLT korlátaiba ütközhetünk. Hiányoznak a dátumokkal kapcsolatos műveletek, és a karakterláncés szám-műveletekből is csak a legalapvetőbbek elérhetőek. Továbbá nem lehetséges számláló ciklus létrehozása6. Ezekre mind megoldást ígér az ajánlás készülő, 2.0-ás verziója [28]. Érdekesség, hogy a 2.0-ás verzió 2001 decembere óta készül, és a négy készültségi szint közül 2005 novemberében jutott el a második, Candidate Recommendation szintre, amely a megvalósíthatóság vizsgálatára fókuszál. Eközben bevett, ám nem kielégítő technikák alakultak ki a problémák megkerülésére, és több „nem szabványos” transzformációs motor is napvilágot látott, melyeket természetesen nem egységesen támogatnak a fejlesztőeszközök és böngészők. Így jelenleg az úgynevezett „kliensoldali XSL transzformáció” nem népszerű technika. A Microsoft nem hivatalos fórumokon kijelentette, hogy csak akkor kezdik el implementálni a 2.0-ás verziót, amikor az ajánlás fázisába kerül. Ennek oka az, hogy az 1.0-ás verziót korábban implementálták, így az Internet Explorer 5.0 verziója végül nem szabványosan működött [30]. A problémák ellenére tartottuk magunkat a kliensoldali XSL transzformációhoz. A dátumok kezeléséhez egy saját, összetett dátum-reprezentációt dolgoztunk ki, és dátumkezelő template-gyűjteményt fejlesztettünk. Az egyéb nehezen megoldható műveletek megoldására JavaScriptet generáltunk. Ezzel ugyan minden probléma megoldható, de a kód bonyolultabb lesz. Fontos még megjegyezni, hogy a kliensoldali XSL transzformáció nagy adathalmazok esetén nagy adatforgalmat és lassú fordítást eredményezhet. Ilyenkor muszáj mégis bevonni a vezérlési réteget, hogy az információhalmazt feldarabolja, és egyszerre csak egy szeletét küldje el – ekkor viszont a leválogatást és sorrendezést mégis a kiszolgálónak kell elvégeznie. Végezetül a javasolt koncepciónak a fejlesztés menetére kifejtett pozitív hatását emeljük ki. A koncepciónak köszönhetően az alkalmazás és a megjelenítés külön-külön is jól
6
A számláló ciklus leválogatásokhoz, például naptár rajzolásához, és az adatként kapott bejegyzések megfelelő helyre kirajzolásához lesz használható. Az ilyen feladatok megoldása imperatív JavaScripttel sokkal bonyolultabb, mint a deklaratív XSLT-vel.
60
tesztelhető. Az XML két oldalán lévő réteg alapvetően különböző kvalitásokkal rendelkező fejlesztőt igényel. Míg a megjelenítési réteg az ergonómia érdekében szépérzéket, empátiát, kreativitást igényel, addig a másik oldal a jó rendszertervezéshez struktúrákban való gondolkodást, precizitást, algoritmuselméletet igényel. A kétfajta fejlesztő(csapat) közötti határozott, szabványokkal rögzített határvonal gördülékeny együttműködést biztosít. Mivel az XML ember által is írható és olvasható, ezért a projektbeli csúszások kisebb hatással vannak egymásra, kisebb a konfliktusok esélye.
3.6.6. Egy példa A 14. és 15. ábrán látható két weblapot ugyanabból az XML tartalomból generálta a böngésző, a kettő fájl közötti egyelten különbség, hogy más XSLT-re hivatkoznak. Az XML fájl megtalálható a függelék 6.4. pontjában. Felhívnánk a figyelmet, hogy míg az első ábra tömör, táblázatos formában közli a mérések információit, addig a második külön lapocskákon, melyek az adott mérés nevére kattintva jelennek meg. A rövidített, teljesen kiírt, és relatív dátumokat a megjelenítő XSLT az egységes dátum formátumból származtatja, ugyanígy az érdemjegyek neveit.
61
14. ábra
15. ábra
62
4. További lehetőségek 4.1. Modellezés lehetőségei A szoftverfejlesztés során egyre gyakrabban dolgozunk programkód helyett modelleken. A legtöbb szoftverfejlesztési módszer hangsúlyozottan alkalmaz különféle modelleket. A legtöbb ilyen modell elsősorban specifikációs és dokumentációs célból készül, nagyban elősegítve a problémakor helyes megismerését, ugyanúgy a megrendelő, mint a fejlesztő részéről. Egy jól megalkotott modellrendszer alapján a fejlesztők nagyobb könnyedséggel készíthetik el a szoftvert. A modellezésnek van azonban egy ma még kevésbé elterjedt felhasználása, a modellből való programkód-generálás. Ez esetben a modell sokkal inkább a programozó fejlesztési eszköze. Egy modell a programkód, vagy annak bizonyos részeinek a váza lesz. Egy modellben foglaljuk össze a programkód bizonyos releváns részeit, a nem releváns információkat pedig az automatikus generátor tölti ki. Jó példa erre az osztálydiagramból való kódgenerálás, ahol például a mezőkhöz való hozzáférő-metódusok (getField, setField) automatikusan készülhetnek el. Az elvet, amely szerint a formális modelljeinkből a tényleges szoftver forráskódja valóban nagyban automatikusan elkészül, Model Driven Architecture-nek (MDA) [33] nevezzük. Az MDA az Object Management Group nemzetközi szervezet ajánlása, valójában egy szabványgyűjtemény. Az ajánlott szabványok (UML, MOF, XMI, CWM) használatát javasolják a fenti eljárás bizonyos lépéseinek megvalósításakor. Egészen gyakorlatias szemmel az MDA annyit jelent egy fejlesztő számára, hogy egy bizonyos tárgyterületnek (pl. entitás-relációk, felhasználói felületek, szekvencia diagramok) létrehozza egy praktikus mértékben absztrakt metamodelljét, majd ezen metamodell szerint alkotja meg a tárgy modelljét. A szoftver forráskódja e modell alapján készül. A metamodell megalkotása során adjuk meg például, hogy az entitásaink rendelkeznek névvel, vannak attribútumai, az attribútumoknak van nevük és típusuk, az entitások között vannak relációk, a relációk kapcsolódásainak van multiplicitása. Egy véges automata metamodellje például megadja, hogy állapotokból áll, van kijelölt kezdőállapota, egy állapot lehet elfogadó vagy elutasító, az állapotok között vannak irányított átmenetek, bemenetre vonatkozó jelöléssel. A metamodelljeinket általában egy rögzített, ún. meta-metamodell szerint alkotjuk, amely egy entitás-relációs diagramhoz hasonlatos. A másik fontos lépés a fejlesztés során az elkészített modellből való kódgenerálás. A kódgenerálást végezhetjük gráf-transzformációval. A gráf-transzformációs végrehajtás során a modellünkből megadott átírási szabályok alapján a programkódot egy az egyben reprezentáló modellé alakul, ebből a tényleges forráskód előállítása már egészen egyszerű, általános. A másik lehetőségünk, hogy a kódgenerálásra "kézzel" készítsünk egy modult, amely imperatív módon előállítja a forráskódot.
4.1.1. Eszközeink Az Egyetem Automatizálási és Alkalmazott Informatikai Tanszékén fejlesztett Visual Modeling and Transformation System [43] használatával a fenti elveknek megfelelően dolgozhatunk. Egy megalkotott metamodellhez a megadott kiegészítő információk alapján a VMTS által adott grafikus környezetben szerkeszthetjük a modellünket. A rendszerben 63
használt kódot reprezentáló modellje a .NET platformon bevezetett CodeDOM [41]. Az imperatív kódgenerálást a Traversing Processor kiegészítés támogatja, amely a metamodell alapján előállítja a metamodell elemeinek futásidejű reprezentációit, és a hozzá tartozó adathozzáférési modulokat. Erre építkezve a kódgenerátorunk a VMTS-ben szerkesztett modellt mint objektumokat kezelheti. Az Eclipse fejlesztőeszközhöz a VIATRA (VIsual Automated model TRAnsform) [42] beépülő modul biztosít hasonló lehetőségeket, ezt a Méréstechnika és Információs Rendszerek Tanszék fejleszti. A keretrendszerben egy teljesen absztrakt metamodellben fogalmazhatunk meg speciálisabb metamodelleket, majd több szint után végül konkrét modelleket készíthetünk. Az elkészült (meta)modellek között transzformációkat fogalmazhatunk meg egy, kifejezetten erre a célra kifejlesztett nyelven. Két modell közötti transzformáció közben generált programkódot tartalmazó fájlokat is létrehozhatunk, így alkalmas a rendszer automatikus kódgenerálásra. [37] Az Eclipse világában egy másik modellezésre alkalmas keretrendszer az Eclipse Modeling Framework (EMF) [35]. Itt az Ecore meta-metamodellen dolgozva adhatunk meg metamodellt, majd a VMTS-beli Traversing Processorhoz hasonlóan az EMF is előállít Java osztályokat és adathozzáférési lehetőségeket. A grafikus szerkesztő előállítását az Graphical Editing Framework és a Graphical Modeling Framework segítségével végezhetjük el könnyedén. Ebben a keretrendszerben a gráf-transzformációs átírásra azonban még nincs lehetőségünk. Az utóbbi két keretrendszer kapcsán fontos tényező, hogy Eclipse beépülő modulként működnek, így a már megszokott fejlesztőkörnyezetünkbe integráltak. Saját Eclipse beépülő készítésével így igen nagyban integráltan működhet a kódgenerátorunk, szinkronizátorunk.
4.1.2. Lehetőségeink A projekt kezdeti fázisában már alkalmaztunk modell alapján való automatikus kódgenerálást, egy későbbi döntés során azonban ideiglenesen elvetettük, mert eszközeink kezdetlegesek és végül elégtelenek voltak (lásd 6.1.4 fejezet). A tárgyalt alkalmazási kör modellezési lehetőségeivel a későbbiekben, önálló laboratóriumok keretében foglalkozunk.
4.2. A Java EE platform további lehetőségei Ebben a fejezetben három olyan továbbfejlesztési lehetőségről teszünk említést, amelyeket a Java EE platform beépített szolgáltatásai biztosíthatnak számunkra, így ezek felhasználása nem igényel plusz fejlesztést.
4.2.1. Vastagkliens alkalmazások A megvalósítási sémában bemutatott struktúra szerint az üzleti logika réteg egy jól specifikált interfészt biztosít a rá épülő alkalmazások számára. Meglepő lehet azonban, hogy a bemutatott séma alapján a felsőbb rétegek mégis egyetlen alkalmazásként épülnek rá az üzleti logikára. Ennek természetesen nem kell szükségképpen így lennie. Mivel az üzleti logika réteg erőforrásai – vagyis a beanekben implementált szolgáltatások – a JNDI címtáron keresztül érhetők el a vezérlési rétegből, így ezeket más – akár Java SE – alkalmazások is elérhetik ugyanezzel a mechanizmussal. Vagyis készíthetők olyan Java SE alapú alkalmazások a rendszerhez, amelyek ugyanúgy igénybe tudjuk venni az üzleti logika réteg funkcióit, mint a webes alkalmazáson keresztül. A legfőbb különbség az, hogy ilyenkor az 64
egyes műveletek végrehajtása – az üzleti logika réteg primitívjeire támaszkodva – kliensoldalon történik. Ezért nevezzük az ilyen alkalmazásokat vastagklienseknek. Az alábbi esetekben lehet érdemes vastagklienseket használni bizonyos funkciók megvalósítására: •
Biztonsági megfontolásokból. Ha ugyanis bizonyos funkciók csak a kliens alkalmazás birtokában érhetők el, az nagyobb biztonságot nyújthat a káros behatolásokkal szemben, mint a mindenki számára elérhető webes felületen történő jelszavas autentikáció.
•
Teljesítménybeli megfontolások miatt. Adatintenzív, adminisztratív jellegű műveletek elvégzése esetén (például: archiválás, biztonsági mentések készítése, adatok importálása).
•
A helyi számítógép erőforrásaival történő hatékonyabb együttműködés érdekében. Például hardverkulcsos autentikáció.
A vastag kliensek használata a vizsgált problémaosztály követelményeiből kiindulva természetesen csak másodlagos, kiegészítő szerepet játszhat.
4.2.2. Az alkalmazásszerver biztonsági mechanizmusai Az alkalmazásszerverek rendelkeznek beépített biztonsági szolgáltatásokkal. Ezeket két csoportra oszthatjuk jellegük szerint: •
Titkosítás, például a kommunikációs forgalom esetén. Ez hatékony védelmet nyújt a lehallgatásos támadások és az illetéktelen adatszerzések ellen.
•
Jogosultságkezelés. A Java EE specifikációba beépített jogosultságkezelési mechanizmus szerepek kezelésére épül. Ezek hozzáféréseit a beanek és a metódusok szintjén egyaránt beállíthatjuk. Lehetőség van ezek kívül olyan szolgáltatásokat készítenünk, amelyek nem a hívó fél jogosultságaival futnak. Ez az előző részben ismertetett vastagkliens alkalmazásoknál is hasznos lehet, hiszen elérhetjük, hogy a vastagkliens által használt bizonyos üzleti primitíveket egyáltalán ne is lehessen igénybe venni a webes felületet vezérlő alkalmazás kódjából.
A megismertetett mechanizmusokat természetesen az alkalmazásszerver szintjén kell konfigurálnunk, amely bizonyos mértékben eltérhet a különböző implementációkban. A részleteket a konkrét alkalmazásszerver dokumentációjában keressük.
4.2.3. Az alkalmazásszerverek clusterbe szervezése Fontos megjegyezni, hogy az alábbiakban bemutatott lehetőségek nem részei a Java EE platform specifikációjának, de megjelenhetnek az egyes implementációkban. Mi a JBoss AS lehetőségeit mutatjuk be, mellyel a függelék 6.3 pontjában részletesebben foglalkozunk. Ha több számítógép is a rendelkezésünkre áll és ezek hálózaton keresztül elérik egymást, akkor megtehetjük, hogy a rájuk telepített JBoss alkalmazásszervereket clusterbe szervezzük. Az így kialakított JBoss AS cluster a kliensek felé egyetlen alkalmazásszerverként látszik, a rajta futtatott beanek szolgáltatásait a clusterben szereplő alkalmazásszerverek bármelyike képes nyújtani. Így a kliensek felől érkező kéréseket a terhelés függvényében kerülnek szétosztásra az alkalmazásszerverek között. A megnövekedett teljesítményen túl fontos előnye még a clusterek kialakításának a hardveres meghibásodásokkal szembeni tolerancia is. Ugyanis ha a cluster egyik alkalmazásszerverét futtató hardver meghibásodik vagy elérhetetlenné válik, a többi alkalmazásszerver átveheti a hozzá beérkezett kérések kiszolgálását.
65
Fontos megemlítenünk azt a tényt, hogy a JBoss AS esetén az alkalmazás beanjeit nem kell különösképpen felkészíteni arra, hogy clusterezett alkalmazásszerveren futtathatók legyenek, így egy rendszer kifejlesztése esetén nem kell már a kezdetekben döntetünk a cluster használatáról, ezt a kifejlesztett rendszer üzembe állításakor, vagy később, a terhelés tartós megnövekedése esetén is ráérünk megvalósítani.
66
5. Összefoglalás és értékelés Dolgozatunkban áttekintést adtunk egyes webes üzleti alkalmazások fejlesztésének lehetőségeiről, elsősorban Java alapú megvalósításra koncentrálva. A kutatásunk során sok eszközt megfelelően kiforrottnak találtunk a megjelölt célra, ennek megfelelően bemutattuk a Java EE 5 és az EJB 3 által nyújtott lehetőségeket. A vezérlés és megjelenítés terén szerzett tapasztalataink alapján azonban egy új koncepciót vezettünk be. A vezérlési réteg elkészítésére adott saját koncepciónk elősegíti a webes üzleti alkalmazások módszeresebb, formálisabb tervezését és építését. A kutatásunk eredményét – a saját fejlesztéseket és a meglévő kiforrott technológiákat – egy kerek egésszé foglaltuk össze. Az ennek eredményeként előálló megvalósítási sémát egy példa-alkalmazáson próbáltuk ki. Ez az alkalmazás 2007 tavaszától a Számítógép Laboratórium VI. tantárgy oktatását fogja segíteni. Az alkalmazás szerkezetének és alapvető funkcionalitásának megvalósítása során a megvalósítási sémánk jól alkalmazhatónak bizonyult. A megvalósítási sémánk a célkitűzésben megfogalmazott alkalmazások fejlesztéséhez közvetlenül felhasználható. Az egyes projektek során természetesen érdemes a sémát finomítani és specializálni, ennek során a séma is fejlődni fog. A sémában szereplő saját fejlesztésű egységeket – a vezérlési és megjelenítési réteget – önálló laborok keretében fogjuk továbbfejleszteni, és ez a megvalósítási séma hasznára fog válni. Az elkészült dolgozat tekinthető a fejlesztési irányvonalakat vizsgáló tanulmánynak. Ilyen tanulmányoknak az iparban pénzre váltható értéke van, és vannak olyan cégek, amelyek hasonló tanulmányok készítéséből élnek. Hisszük, hogy azok a fejlesztőcsapatok, amelyek a célkitűzésben leírt problémaosztályba tartozó feladatokon dolgoznak, sokat profitálhatnak a dolgozatban leírtakból még akkor is, ha nem az általunk kidolgozott sémát választják végül.
67
6. Függelék 6.1. Csoportmunka eszközök Egy fejlesztés hatékonyságát nem csak a felhasznált technológiák határozzák meg, hanem a csoportmunka is. Ezért a példaalkalmazás és a dolgozat fejlesztésnél sok energiát fektettünk a megfelelő csoportmunka-eszközök és környezet kialakításába. Ez már középtávon is visszahozta a befektetett munkát. A csoportmunkának három összetevője volt: a kommunikáció, a dokumentumok tárolása és a forráskód tárolása. Ezekre mind kiforrott, központosított megoldást alkalmaztunk. Kommunikáció A kommunikáció alapvető eszköze egy e-mail levelezőlista volt, amelyben a projektben közvetlenül résztvevő emberek voltak a tagok. Ezen kívül a gyors azonnali kommunikációhoz azonnali üzenetküldő szolgáltatást használtunk. A konkrét levelezőlista kiszolgáló a sziami.cs.bme.hu címen elérhető Mailman, az azonnali üzenetküldő szolgáltatás az MSN Messenger volt, mivel egyéb projekteink kapcsán is ezeket használtuk, ezek voltak könnyen elérhetőek. Dokumentumtárolás Az alakuló dokumentációt és terveket egy wiki-n tároltuk. A wiki egy webportál típus, mely ún. wikilapokból áll. Ezek alapvető formázásokat tartalmazó szövegek, melyeket bármely bejelentkezett felhasználó átszerkeszthet a böngészője segítségével. A lapok verziókövetettek, és a nevük automatikusan linkké alakul az oldalakon. Minden információt, kérdést, választ, döntést a kimondásának pillanatában felvittünk a wikire. Ezzel egy központi írásos anyag alakult ki, ami segítette, hogy az összes résztvevő fejében azonos kép legyen a projektről. A konkrét wiki a FlexWiki volt, mely a sokfajta wiki között kevéssé ismert és támogatott. A népszerű wikikhez képest néhány zavaró hiányossága van, ám több olyan ötlet és kényelmi szolgáltatás van benne, mely miatt megéri használni. A legnagyobb hiányosság a fájlfeltöltés volt, a problémát azzal kerültük meg, hogy a fájlokat (képek, diagramok) ki-ki a saját webtárhelyére töltötte fel, és a wikilapokon linkeket helyezett el. Komoly előny volt az egyszerű telepítés (egyetlen tömörített állomány kicsomagolása), a felület teljes testreszabhatósága, mely a navigálást gyorsabbá tette. A legnagyobb előny, hogy a lapokhoz szabadon lehet metaadatokat kapcsolni, amelyek egy részét a rendszer automatikusan felhasznál, másokat a lapokba ágyazott WikiTalk szkriptekkel lehet felhasználni. A linkek fölé húzva az egeret a rendszer megjeleníti a hivatkozott lap Summary (összefoglaló) metaadatát, így a projekt nyitólapján linkelt 40 Wikilap között sokkal gyorsabb volt megtalálni azt, amit keresünk. Jelen dolgozat megírásakor a tartalmat szekciókra bontottuk, mindegyik szekció egy-egy WikiLapra került, és itt az összefoglalón kívül metaadatban tároltuk, hogy ki írja, és mi hiányzik még. Ezeket az információkat, a szekció aktuális hosszával együtt egy szkript kigyűjtötte a tartalomjegyzék WikiLapon a címek mellé. Így a dolgozat fejlesztésének állapota egyetlen képernyőn nyomon követhető volt. Ezt szemlélteti a 16. ábra. A nyilak utáni kisbetűs szöveg automatikusan generálódik a hivatkozott lapok metaadataiból. 68
16. ábra – Az éppen készülő TDK tartalomjegyzéke.
Forráskód tárolása A rendszer készítésekor fejlesztett projektek forráskódjait a Subversion (SVN) verziókövető rendszerrel kezeljük. Az SVN repository (tároló) az egyetem egyik számításelmélet tanszéki szerverén foglal helyet. A verziókövető rendszerek alapvető fontosságúak egy csoportos fejlesztés esetén. Első előnye, hogy mivel a forráskódok aktuális változatai központi szerver foglalnak helyet, bárki bármikor hozzáférhet az aktuális változathoz. A verziókövetésnek köszönhetően minden módosításról részletes naplózás készül, így utólag szükség esetén könnyedén visszaállítható egy korábbi verzió. A módosításokat megtekinthetjük olyan nézetben is, melyben a módosítás előtti és utáni forráskód együtt látszik, kiemelve a módosított részeket, így jól visszakövethető utólag a fejlesztés története. A rendszer segítségével továbbá megoldható, hogy a csapat több tagja olyan funkciókon dolgozzon párhuzamosan, melyek közös forrásfájlokat érintenek, mert igen jó támogatást ad a többek által módosított forrásfájlok összefésülésére.
69
6.2. A példaalkalmazás fejlesztési projektje A Számítógép Laboratórium VI. tantárgy keretein belül a hallgatók méréseket végeznek, melyek beugróval kezdődnek, majd laborvezető segítségével mindenki elkezd egy egyéni feladatot, ezt otthon befejezi, és három napon belül beadja a hozzá elkészített jegyzőkönyvet. A folyamat irányítására és a szükséges oktatói háttérmunka megkönnyítésére egy webes alkalmazás szolgál. Az problémaosztályunk példaalkalmazásának ezt a rendszert jelöltük ki, és a fejlesztési sémát az alkalmazás újraírásával próbáltuk ki a gyakorlatban.
6.2.1. Kiindulási anyagok, a korábbi rendszer A korábbi rendszer PHP szkriptnyelven íródott, és Oracle adatbázisban tárolta az adatokat. A rendszerről rendelkezésre áll egy fejlesztői dokumentáció, mely röviden leírja a Hallgató, Laborvezető, Értékelő és Adminisztrátor szerepeket, az általuk bejárható állapotokat és a közlendő információkat. A relációs adatbázis leírása is megtalálható benne. Végül a konkrét megvalósítás sarkalatos pontjait tárgyalja: az adatbázishoz kapcsolódást, a munkamenet kezelést és néhány nézet megvalósítását. Utólag visszatekintve a dokumentáció második két fejezete számunkra érdektelen, ugyanis ezekre kész modulokat használunk, melyeknek csak a paramétereit kellett specifikálni. Ez egy jó fokmérője annak, hogy sikerült a korábbinál hatékonyabb fejlesztést támogató megoldást választanunk. Az új rendszer elkészítéséhez egy követelménylistát kaptunk, mely a rövid és hosszú távon elvárt funkcionalitást tartalmazza felsorolás szinten. A korábbi rendszer leírása és a szereplőkkel való beszélgetések alapján egy saját specifikációt dolgoztunk ki, melynek teljességvizsgálatához nagyszerű volt az elvárás-lista.
6.2.2. Specifikáció A projekt az egyedi szoftverfejlesztés megrendelések tipikus jellemvonásait viselte. A csoport kevéssé ismerhette az oktatói háttérmunka munkamenetet, és az itt évek alatt kialakult szakzsargont. A korábbi dokumentumok és „interjúk” alapján kellett feltérképezni a működést. Az új rendszer fejlesztéséhez egy elvárás-listát kaptunk, amelyet ellenőrző listának is használtunk a specifikáció teljességének vizsgálatakor. A projekt viszonylagos egyszerűsége miatt nem vettünk elő nagy projektirányítási módszertanokat, viszont ezek kulcsfogalmait, eszközeit és alapötleteit szabadabb formában felhasználtuk. Első lépésként fogalomtárat építettünk és szöveges use-case-eket fogalmaztunk meg. Ezek után megalkottuk a modell objektumainak terveit egyszerűsített class diagramon. Ezzel párhuzamosan néhány kevésbé triviális folyamatot diagramon ábrázoltunk. Végül a lehetséges felhasználói interakciókat meghatározó állapotgépet rajzoltuk meg.
6.2.3. Fogalomtár és use-case-ek Első lépésként a fogalmak felderítésére és definiálására volt szükségünk. Ezzel párhuzamosan use-case-eket fogalmaztunk meg. Erre egy-egy wikilapot (lásd 6.1.) használtunk. A FogalomTár lapon a fogalmakat, és néhány mondatos, minél szigorúbb definíciójukat tároltuk. A felsorolásnál külön választottuk azokat a fogalmakat, melyek a
70
problémából adódtak (pl. Mérésguru), és azokat az absztrakt fogalmakat, melyeket mi vezettünk be (pl. Szerepkör). A UseCase lapot a FogalomTár draftjának elkészülte után kezdhettük el. Az egyes szerepköröket felsoroltuk, majd az általuk végrehajtható műveleteket. A műveletek leírása során előkerülő új kifejezésekkel a fogalomtárat bővítettük; így az ott lévő definíciók is pontosodtak, amik a use-case-eket befolyásolták. Továbbá a két dokumentumot többször is egyeztettük a „megrendelőkkel”. Több iteráció után a két lapot a fejlesztők és a megrendelő is konzisztensnek, és az alapfunkcionalitást tekintve teljesnek találta – ez volt az első fontos mérföldkő a projektben.
6.2.4. Class Diagram Miután a fogalmak letisztázódtak, és a szerepkezelés modelljét is meghatároztuk, elkezdtük az implementálandó objektumokat tervezni. Ehhez a StarUML Class Diagram funkcióját használtuk, ám az UML-nél szűkebb szemantikával. Az attribútumok típusának a Java egyszerű beépített típusai közül engedtünk meg néhányat, és metódusokat nem ábrázoltunk. Az aggregációt is egyszerű asszociációkkal ábrázoltuk, a kapcsolat irányítása jelzi, hogy melyik objektum tartalmazza melyiket, és a végződések neveiben tároltuk, hogy milyen attribútumon keresztül navigálható a kapcsolat. Az asszociációk között nem engedtünk meg több-több kapcsolatot. A megkötések azért voltak szükségesek, mert az eredeti tervünk az volt, hogy a StarUML XML exportjából automatikusan generáljuk az objektumok kódját a Hibernate annotációkkal és a vezérlési réteg csomagoló-objektumaival együtt. A terv zátonyra futott, mert kiderült, hogy nem elég az UML class diagram szemantikáját szűkítenünk, átértelmeznünk, hanem bizonyos pontokon bővítésekre van szükségünk. Ilyen probléma például, hogy a Hibernate annotációkban megadható, – és a teljesítmény szempontjából nagy jelentősséggel bír – hogy egy adott asszociáció mentén a kapcsolódó objektumokat azonnal (eager) vagy csak igény esetén (lazy) töltsük be. A konkrét rendszerben például a Szerep>Felhasználó irányhoz eager betöltés ideális, miközben fordított irányban lazy. A kódgenerálásra alkalmas modellezéshez tehát szabadabb modellezőeszközre van szükségünk. Erre két jó jelölt az AUT tanszéken fejlesztett VMTS, vagy a MIT tanszéken fejlesztett VIATRA. Ezek megfelelő szintű elsajátítására azonban nem volt idő a project keretein belül, de a továbbfejlesztések során igénybe kívánjuk venni valamelyiket.
6.2.5. Állapotgép Amikor elkezdett kialakulni a vezérlési réteg, úgy döntöttünk, hogy a felhasználó kapcsolata a rendszerrel egy állapotgép működésének feleltethető meg. Az egyes állapotok egy-egy képernyőnek (weblapnak) felelnek meg. A modellezés során látszott, hogy egy-egy állapotnak alállapotokat is kell tartalmazniuk. Modellünkben egy alállapotból azonnal végrehajtható egy olyan átmenet, ami az őt tartalmazó állapotban végrehajtható.
6.2.6. Kliensalkalmazás A konkrét példaalkalmazásunkban felmerült az igény arra, hogy a funkcionalitás egy részét ne a webes felületen valósítsuk meg. A félév kezdetekor a hallgatókhoz feladatot kell rendelni, és be kell osztani őket öt laboralkalomból álló laborsorozatokba. Ehhez a feladathoz fel kell használni az egyetemi nyilvántartásból (Neptunból) származó adatokat, a javasolt időpontokat egyeztetni a
71
hallgatókkal, majd az elkészült adathalmaz alapján létre kell hozni az alkalmazás modell rétegének objektumait. Ezt a feladatot minden félév elején egyszer kell végrehajtani. Ezt a feladatot tipikusan egy, vagy néhány adminisztrátor végzi el, és közben szükség van a gépének helyi fájlrendszeréből állományok felolvasására, írására, és nyomtatásra. Ezek mind olyan elvárások, amelyek egy vastag klienssel jobban megvalósíthatóak: az implementáció és a használat is egyszerűbb. Ez a helyzet nem egyedi, nem csak a példaalkalmazásra jellemző. Képzeljünk el egy webes áruházat, amelynek mintaboltja is van; a bolti eladók pénztárgépein futó alkalmazásnak inkább egy vastag kliens megfelelő a speciális hardverek kezeléséhez. Az előző rendszerben különálló szkriptek és táblázatkezelő alkalmazás segítségével oldották meg a feladatot, majd a végeredményt megfelelő formátumra hozták, és importálták a rendszerbe. Mivel a megvalósítási sémánk alapjául az EJB3 szabványt választottuk, egy másik megoldás is kínálkozik: lehetőség van arra, hogy egyszerű java alkalmazások kliens oldalról elérjék a webes alkalmazás modell rétegét képző távoli beaneket. A konkrét példa kapcsán azért is hasznos külön adminisztrátori kliensalkalmazást fejleszteni, mert a félév kezdetén és a félév folyamán az üzleti folyamatot más-más nézőpontból szemléljük, és a két nézőpont más-más egyszerűsítéseket enged meg a modellben. Erre példa, hogy a félév előkészítésekor egy hallgatót a „páros hét csütörtök 14 óra” laborsorozatba osztjuk be, viszont a félév folyamán kényelmes a hallgató öt mérésének időpontját külön-külön, függetlenül tárolni, így a kivételek és a pótmérések kezelése nem okoz bonyodalmat. Tehát az adminisztrátor kliensalkalmazásban egy külön modell szerint készítjük elő az adatokat, majd az alkalmazás ezt a modellt transzformálja a webes alkalmazás modelljébe, és a konkrét beaneken végre is hajtja a műveleteket. Megjegyezzük, hogy ebben a tekintetben eléggé speciális a példaalkalmazás, hiszen az adminisztrátor féléves rendszerességgel egy bonyolultabb modellben adja meg az adatokat, azokon szűkítő leképezést hajtunk végre, és ennek megfordítására sosincs szükség.
6.3. A futtatókörnyezet 6.3.1. A JBoss alkalmazásszerver A JBoss [38] egy amerikai cég, amely nyílt forráskódú, szabadon felhasználható köztesszoftverek fejlesztésével foglalkozik. A JBoss Application Server [39] is egy ilyen termékük, amely a Java EE specifikációival kompatibilis. A jelenlegi legutolsó stabil változata a JBoss AS 4.0.5.GA [40], amely 2006. október közepén jelent meg. Fontos tudni, hogy ez nem egy Java EE 5.0 kompatibilis alkalmazásszerver, hanem a J2EE 1.4 specifikációját teljesíti – a Java EE 5.0 kompatibilis JBoss AS 5.0 fejlesztése még nem érte el a béta állapotot. Ennek ellenére mégis tudtuk használni az új specifikációnak megfelelő alkalmazásunk futtatására, hiszen a rendszernek létezik egy olyan kiegészítése, ami lehetővé teszi az EJB 3.0 alapú beanek futtatását. A JBoss AS használata mellett a következő pozitív tulajdonságai miatt döntöttünk: •
Mint már említettük, teljesen ingyenes és a forráskódja is hozzáférhető.
72
•
A piacon széles körben elterjedt, jól dokumentált és nagy, segítőkész online közösség áll mögötte.
•
Robosztus, és az alkalmazásszerverek clusterbe szervezhetők.
•
Az általunk használt Hibernate rendszert integrálva tartalmazza.
6.3.2. Adatforrás konfigurálása A perzisztencia egységet alkotó JAR állományt tehát az alkalmazásszerver futtatja. Ennek paramétereit a korábban már bemutatott persistence.xml állomány definiálja. Az ebben megjelölt adatforrást – a hivatkozott példában ez a java:/DefaultDS volt – természetesen konfigurálnunk kell az alkalmazásszerveren. Erre a JBoss AS esetében ún. Data Source leíró XML állományban van lehetőségünk. A fent említett adatforrás a rendszerben alapértelmezett adatforrás, ami egy Hypersonic SQL adatbázist használ. A konfigurációja a következőképpen néz ki: <jndi-name>DefaultDS jdbc:hsqldb:${jboss.server.data.dir}${/}hypersoni c${/}localDB org.hsqldb.jdbcDriver <user-name>sa <password> <min-pool-size>5 <max-pool-size>20 <security-domain>HsqlDbRealm ... ...
Nézzük meg mit jelentenek az itt megjelenő opciók: A jndi-name az adatforrásnak a már említett JNDI címtér beli címére utal. Megjegyezzük, hogy a java:/ előtagot itt nem kell kitennünk. A connection-url egy JDBC adatbázis kapcsolatot leíró karakterlánc, amely a kapcsolat elérési paramétereit definiálja. A driver-class a kapcsolat kezeléséhez használt JDBC osztályra utal. Ezeken kívül megadhatjuk még a kapcsolódáshoz használt felhasználói nevet és jelszót, az egyidejűleg nyitva tartott kapcsolatok minimális és maximális számát, valamint például az erőforráshoz rendelt biztonsági tartományt (security domain), amivel az erőforráshoz történő hozzáféréshez jogosultságokat köthetünk. Ugyanez a konfiguráció egy Oracle adatbázist használó adatforrás esetén az alábbiak szerint alakul:
Az itt megjelenő transaction-isolation opció az Oracle Database számára azt határozza meg, hogy az érkező kéréseket a meghatározott izolációs szinten hajtsa végre. Az itt megjelölt – a szokásosnál lazább – Read Committed izolációs szintet azért alkalmazhatjuk, mert az alkalmazásunk során a már bemutatott optimista zárolás stratégiát használjuk.
6.4. Egy állapot XML A következő XML dokumentum példa a vezérlési réteg kimenetére, és a megjelenítési réteg bemenetére. Az ebből generált weblapok láthatóak a 3.6.6. részben az 14. és 15. ábrákon. <model> Ez a feladattípus egy videótéka elkészítéséról szól. A videótékában kazetták vannak, melyeket ügyfelek tudnak kikölcsönözni. A kazettának a filmcím a jellemző adata, az ügyfélnek a neve és lakcíme. <meresek> <MeresInfo meresJegy="5"> 4 <jegyzokonyv letoltesActionID="letolt42" jegyzokonyvJegy="5"/> <MeresTipusInfo nev="Oracle" sorszam="1"/> <MeresInfo meresJegy="-1"> -1 <jegyzokonyv jegyzokonyvJegy="-1"/> <MeresTipusInfo nev="XSQL" sorszam="5"/>
7. Irodalomjegyzék A webes források esetén a megtekintés időpontja: 2006. október 26. [1] Steve Burbeck, Ph.D.: How to use Model-View-Controller (MVC) Applications Programming in Smalltalk-80™ tanulmány része, 1992 http://st-www.cs.uiuc.edu/users/smarch/st-docs/mvc.html [2] A Moodle szoftver holnapja http://moodle.org [3] Moodle szoftver teljesítményéről szóló fórum http://moodle.org/mod/forum/view.php?f=94 [4] Hypertext Preprocessor – PHP http://www.php.net/ [5] The Common Gateway Interface – World Wide Web Consortium http://www.w3.org/CGI/ [6] The Java Servlet Technology – Sun Microsystems Inc. http://java.sun.com/products/servlet/ [7] Richard Monson-Haefel, Bill Burke, Sacha Labourey: Enterprise JavaBeans Fourth Edition, O'Reilly Media, 2004 [8] Bill Burke, Richard Monson-Haefel: Enterprise JavaBeans 3.0 Fifth Edition, O'Reilly Media, 2006 [9] Christian Bauer, Gavin King: Hibernate in Action Manning Publications, 2004 [10] Java Specification Requestek teljes gyűjteménye - Sun Microsystems http://jcp.org/en/jsr/all [JSR77] J2EE Management http://jcp.org/en/jsr/detail?id=77 [JSR151] JavaTM 2 Platform, Enterprise Edition 1.4 (J2EE 1.4) Specification http://jcp.org/en/jsr/detail?id=151 [JSR153] Enterprise JavaBeansTM 2.1 http://jcp.org/en/jsr/detail?id=153 [JSR176] J2SETM 5.0 (Tiger) Release Contents http://jcp.org/en/jsr/detail?id=176 [JSR220] Enterprise JavaBeansTM 3.0 http://jcp.org/en/jsr/detail?id=220 [JSR244] JavaTM Platform, Enterprise Edition 5 (Java EE 5) Specification http://jcp.org/en/jsr/detail?id=244 [JSR907] JavaTM Transaction API (JTA) http://jcp.org/en/jsr/detail?id=907 [JSR914] JavaTM Message Service (JMS) API http://jcp.org/en/jsr/detail?id=914 [11] Java 1.4 Enterprise Edition Compatible Implementations http://java.sun.com/j2ee/compatibility_1.4.html [12] Java Enterprise Edition 5 Compatible Implementations http://java.sun.com/javaee/overview/compatibility.jsp [13] IBM WebSphere Application Server Product Overview http://www-306.ibm.com/software/webservers/appserv/was/
76
[14] Oracle Application Server http://www.oracle.com/appserver/index.html [15] SAP NetWeaver http://www.sap.com/platform/netweaver/index.epx [16] Sun Application Server http://www.sun.com/software/products/appsrvr/index.xml [17] Gajdos Sándor: Adatbázisok Műegyetemi Kiadó, 2006., jegyzetszám: 55053 [18] Relational Persistence for Java and .NET – Hibernate http://www.hibernate.org/ [19] Govind Seshadri : Understanding JavaServer Pages Model 2 architecture JavaWorld.com, 1999. 12. 29. http://www.javaworld.com/javaworld/jw-12-1999/jw-12-ssj-jspmvc.html [20] Millstone http://www.millstone.org/ [21] Demystifying Jakarta Struts http://www.coreservlets.com/Apache-Struts-Tutorial/ [22] Apache Struts 1.x User Guide http://struts.apache.org/1.x/userGuide/index.html [23] Tapestry User Guide http://tapestry.apache.org/tapestry4/UsersGuide/index.html [24] Object Graph Navigation Language http://www.ognl.org/ [25] Basic HTTP as defined in 1992 http://www.w3.org/Protocols/HTTP/HTTP2.html [26] Hypertext Markup Language - 2.0 http://www.ietf.org/rfc/rfc1866.txt [27] XSL Transformations (XSLT) Version 1.0 http://www.w3.org/TR/1999/REC-xslt-19991116 [28] XSL Transformations (XSLT) Version 2.0 http://www.w3.org/TR/xslt20/, [29] XML User Interface Language (XUL) http://www.mozilla.org/projects/xul/ [30] The Business Case for XSLT 2.0 http://metaphoricalweb.blogspot.com/2004/12/business-case-for-xslt-20.html [31] „DONTCLICK.IT Intsitute for Interactive Research” Alex Frank diplomamunkájához tartozó bemutató http://www.dontclick.it/ [32] Reena Hanumansetty: Model Based Approach for Context Aware and Adaptive user Interface Generation diplomamunka, Virginia Polytechnic Institute and State University, 2004 http://scholar.lib.vt.edu/theses/available/etd-08242004-120131/unrestricted/MastersThesis.pdf [33] Model Driven Architecture - A Technical Perspective (Document number ormsc/2001-07-01) OMG Architecture Board MDA Drafting Team http://www.omg.org/cgi-bin/apps/doc?ormsc/01-07-01.pdf [34] Eclipse Modeling Framework, Graphical Modeling Framework: http://www.eclipse.org/emf/ http://www.eclipse.org/gmf/
77
[35] Eclipse Development using the Graphical Editing Framework and the Eclipse Modeling Framework IBM 2004. http://www.redbooks.ibm.com/abstracts/sg246302.html [36] Jósvai Eszter, Tóth Dániel: Felhasználói felületek modell-alapú fejlesztése TDK dolgozat BME VIK, 2005. november http://sztk.sch.bme.hu/files/tdk/12/josvai_toth.pdf [37] Tóth Dániel: Grafikus felhasználói felületek modell-alapú fejlesztése diplomamunka BME VIK, 2006. május [38] JBoss honlapja http://labs.jboss.com/portal/ [39] JBoss Application Server http://labs.jboss.com/portal/jbossas [40] JBoss Application Server Product Datasheeet http://www.jboss.com/pdf/JBossASBrochure-Jun2005.pdf [41] .NET Framework Developer's Guide - Dynamic Source Code Generation and Compilation http://msdn2.microsoft.com/en-us/library/650ax5cx.aspx [42] Visual Automated model Transformations http://dev.eclipse.org/viewcvs/indextech.cgi/~checkout~/gmt-home/subprojects/VIATRA2/index.html [43] Visual Modeling and Transformation System http://vmts.aut.bme.hu/