Testreszabható Java modulok készítése

Posted by | No Tags | Szoftverfejlesztés | Nincs hozzászólás a(z) Testreszabható Java modulok készítése bejegyzéshez

Üzleti igényeket kielégítő alkalmazások fejlesztése közben előfordul, hogy hasonló igényeket kell újra és újra megvalósítanunk. Ilyenkor – mivel senki nem szereti ismételni magát – felmerül az igény, hogy ezeket jó lenne újrafelhasználható, moduláris módon megoldani. Igen ám, de ezek az igények általában csak hasonlóak, nem identikusak, ami viszont már felvet kérdéseket, problémákat. A következőkben bemutatok egy módszert, ami megold néhány ilyen nehézséget, és általa testreszabható, bővíthető modulokat készíthetünk Java nyelven.

Miről is van itt szó?

Hogy könnyebben átlátható legyen, mire is gondolok, vegyük példának a felhasználókezelést. Nagylelkűen tekintsünk el attól, hogy sok kész megoldás létezik rá, a példa kedvéért mondjuk, hogy magunk szeretnénk elkészíteni. Tegyük félre az autentikációt is, és koncentráljunk egyszerűen csak arra, hogy a felhasználóknak vannak adatai, amit karban szeretnénk tartani. Emellett tegyük fel, hogy lesznek más modulok, funkciók, amik függnek a felhasználó objektumunktól, ezért szükség van egy általános megoldásra.
A cél egy olyan megvalósítás, ami gyorsan behúzható a projektjeinkbe, és szükség esetén nagyon egyszerűen módosítható. Bónuszpont jár, ha az esetleges későbbi javításokat elég egyetlen, közös helyen megtenni, és az érvényesül(het) minden, a modult felhasználó projektben.

Hogyan érjük ezt el?

A legkényelmesebb az lenne, ha ténylegesen leprogramoznánk a modult és minden funkcionalitást, beleértve az adatbázis réteget, entitásokkal, majd raknánk fölé egy UI-t, és végül ezt a teljes modult használnánk mindenhol. Csak hát ez nem járható út, hiszen ha egyszer megírtuk az entitásokat, akkor azok az őket felhasználó projektből már nem módosíthatóak. Ha nem vettünk fel pl. telefonszám mezőt a User entitásba, akkor az már sehogy nem kerül bele. Igaz? Hamis!
Az EclipseLink JPA implementáció ugyanis ad egy nagyon hasznos eszközt a kezünkbe: a @VirtualAccessMethods annotációt. Ennek segítségével jelölhetjük, hogy az entitásunk bővíthető, és módot ad arra, hogy virtuális tulajdonságokat (property) definiáljunk, anélkül, hogy a forrásfájlt módosítanánk, vagy a persistence unitot újra deployolnánk.

Vegyük például a következő osztályt:

@Entity
public class User {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

Az annotáció mellett biztosítanunk kell egy módszert a JPA számára, amivel a virtuális tulajdonságokhoz tartozó értékeket memóriában eltárolhatja. Ehhez biztosítanunk kell egy getter és setter metódust (ezeknek a neve az annotációban felüldefiniálható), és mögé kell tennünk valamilyen adatszerkezetet. Egy map például jó választás lehet, természetesen tranziens módon, hiszen magát a mapet nem akarjuk perzisztálni. A kiegészített osztály így néz ki:

@Entity
@VirtualAccessMethods
public class User {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    private String name;

    @Transient
    private Map<String, Object> extensions = new HashMap<>();

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public <T> T get(String name) {
        return (T) extensions.get(name);
    }

    public Object set(String name, Object value) {
        return extensions.put(name, value);
    }

}

Ezzel már felkészítettük az entitásunkat arra, hogy virtuális, azaz az osztályban előre nem rögzített mezőket tudjon kezelni. A következő lépés, hogy meg kell tudnunk mondani, mik ezek az extra mezők. Ez az eclipselink-orm.xml fájlban történik: itt van lehetőségünk a mezőket leíró adatok megadására. A fájl helye a META-INF-ben van, a persistence.xml mellett. A neve egyébként a persistence.xml-ben felüldefiniálható. Mivel a persistence.xml már a felhasználó projektben helyezkedik el, ezért az extra mezők hozzáadása is történhet a felhasználó projektben.
Ha például a telefonszámmal és a születési dátummal szeretnénk még a felhasználót bővíteni, akkor az eclipse-link-orm.xml a következőképpen nézhet ki:

<?xml version="1.0"?>
<entity-mappings
    xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/orm"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.eclipse.org/eclipselink/xsds/persistence/orm http://www.eclipse.org/eclipselink/xsds/eclipselink_orm_2_1.xsd"
    version="2.1">

    <entity class="com.wcs.jeewcslib.idm.api.entity.User">
        <attributes>
            <basic name="phone"
                   access="VIRTUAL"
                   attribute-type="String">
                <column name="PHONE"/>
            </basic>
            <basic name="birthdate"
                   access="VIRTUAL"
                   attribute-type="java.util.Date">
                <temporal>TIMESTAMP</temporal>
                <column name="BIRTHDATE" />
            </basic>
        </attributes>
    </entity>
</entity-mappings>

Mint látszik, az xml-ben megadjuk az entitást, amit bővíteni szeretnénk, majd felsoroljuk az extra tulajdonságokat. Meg kell adnunk a leképezés típusát (jelen esetben ez mindkétszer basic), amiben definiáljuk a tulajdonság nevét, típusát, valamint jelezzük, hogy virtuális tulajdonságról van szó. Ezen felül meg kell még adnunk az oszlop nevét, amire le szeretnénk képezni adatbázisban. A születési dátumnál használtuk még a <temporal>TIMESTAMP</temporal> taget: ez a @Temporal(TemporalType.TIMESTAMP) annotációnak felel meg. Mint ez a példa is mutatja, az xml-es megadási módnál is megtaláljuk az annotációval megadható beállítások megfelelőjét, azaz ugyanúgy tudunk definiálni mindent ezzel a módszerrel is, mint ha annotációkkal, az osztály forrásában deklarálva tennénk. Az eclipselink-orm.xml-ről további információk itt, a teljes módszerről pedig itt találhatóak.

Ha ezt megtettük, akkor már nincs más hátra, mint felvenni adatbázisban a megfelelő oszlopokat, és a rendszerünk máris képes ezeket kezelni. Adatbázis verziókövetésére kiváló eszköz a Liquibase, amihez elérhető Netbeans plugin LiquiFace néven.

Mi a helyzet a felhasználói felülettel?

Mint írtam, a célunk az lenne, hogy egy out-of-the-box megoldást készítsünk, ami mégis testreszabható. Az entitás bővíthetőségével a testreszabás már megoldott, de még nem tökéletes, hiszen felhasználói felület nincs az adatok karbantartására. Alapvetően felmerül a kérdés, hogy szükséges-e ezt is újrafelhasználható módon elkészíteni, hiszen a megjelenítési réteg különböző projektekben sokszor teljesen eltérő lehet, ezért ugye nem biztos, hogy van értelme általános felülettel készülni. Abban az esetben azonban, ha mégis ugyanazt a technológiát használjuk több projektben is, máris nagyon hasznos, ha van kész megoldásunk.

Ilyen esetekben lehet nagyon hasznos az automata űrlap generálás. Korábban már írtam egy bejegyzést a Metawidget működéséről, ezért ennek a részletes ismertetésétől most eltekintek. Röviden annyi az alapelv, hogy a rendelkezésre álló metaadatok alapján (Java osztályok, annotációk, stb.) az eszköz űrlapot generál az entitásunkhoz, egyenként megvizsgálva minden propertyt, eldöntve, milyen komponenst készítsen hozzá.

A gyakorlatban mi ezt úgy oldottuk meg, hogy a felhasználó projektben a bővítésre kerülő entitáshoz készítünk egy burkoló osztályt, ami implementál egy egységes interfészt, jelölve, melyik entitást bővíti. A fenti példához mondjuk a következő osztályt készítenénk:

public class UserView implements EntityView<User> {

    private User user;

    @Override
    public void wrap(User user) {
        this.user = user;
    }

    public Long getId() {
        return user.getId();
    }

    public void setId(Long id) {
        user.setId(id);
    }

    public String getName() {
        return user.getName();
    }

    public void setName(String name) {
        user.setName(name);
    }

    public String getPhone() {
        return user.get("phone");
    }

    public void setPhone(String phone) {
        user.set("phone", phone);
    }

    public String getBirthdate() {
        return user.get("birthdate");
    }

    public void setBirthdate(Date birthdate) {
        user.set("birthdate", birthdate);
    }

}

Az EntityView interfész pedig így néz ki:

public interface EntityView<T extends Object> {
    void wrap(T t);
}

A burkoló osztálynak két szerepe van: egyrészt a felhasználó projektben tudjuk arra használni, hogy típusosan érjük el az extra tulajdonságokat, másrészt átadjuk az előre elkészített felhasználói felületnek, ami így képes a megjelenítésbe belevenni a plusz adatokat. Kicsit részletezve: álljon a felületünk a felhasználók egy listájából, ahol egy felhasználót kiválasztva az adatai egy űrlapon szerkeszthetőek. Ezen felül tudjunk új felhasználót létrehozni, meglévőt törölni.

Ebben az esetben a listát és a funkciógombokat teljes mértékben elkészíthetjük előre, pusztán az űrlap kell, hogy minden esetben egyedi dolgokat tartalmazzon. Itt játszik szerepet egyébként az implementált interfész: mivel a felület mögötti logika tudja, hogy az átadott osztály egy EntityView, egy felhasználó kiválasztása után képes példányosítani az osztályt, és a wrap metódussal átadni neki a felhasználót. A beburkolt felhasználópéldányból pedig a Metawidget segítségével már tudunk megfelelő űrlapot generálni. Ha ez már megy, akkor az elképzelést tovább is lehet vinni, és a Metawidget segítségével, meg némi hozzáadott saját fejlesztéssel azt is el tudjuk érni, hogy már a lista oszlopaiban is megjelenjenek az extra mezők, és ezáltal tényleg out-of-the box megoldást adhatunk.

Az alábbi két képen látható egy generált lista és egy generált űrlap: a telefon mező mindkét esetben virtuális property.

Generált lista virtuális telefon oszloppal

Generált lista virtuális telefon oszloppal

Generált űrlap virtuális telefon mezővel

Generált űrlap virtuális telefon mezővel

Összegzés

A fenti módszerrel elkészített modulunkat szépen saját jar-ba lehet csomagolni, amit utána bármilyen projektben egyszerűen felhasználhatunk. A megoldásnak megvan az az előnye, hogy ha utóbb javításokra vagy új fejlesztésekre van szükség, a jar frissítésével egyszerűen frissíthető minden felhasználó projekt. Egyedire szabni pedig összesen annyiból áll, hogy létrehozunk egy burkoló osztályt, és felvesszük az eclipselink-orm.xml-be a hiányzó mezőket. A célt tehát teljesítettük: az elkészült modul újrafelhasználható, testreszabható és még karbantartani is egyszerű!


No Comments

Leave a comment