JavaFX-sovellus MVP-arkkitehtuurilla

JavaFX:n databindaus voi olla ongelmallinen toteuttaa. Tähän tarvittava syntaksi on monimutkainen ja elementteihin pitää liittää paljon vaikeaselkoista boilerplate-koodia mm. tapahtumanhallintaan. Ei ole selvää, onko datan sidonnasta saatava hyöty suurempi kuin sen vaatima kustannus.

Toinen ongelma on JavaFX:n property-luokkien käyttö. Olisi luettavampaa käyttää sovelluslogiikkakerroksessa tavallisia POJO-luokkia, eikä syntaksisesti hankalia property-luokkia. Javassa property-luokkien käyttö rajoittuu JavaFX-sovelluksiin, joissa JavaFX:n datan sidonta (databindaus) on toteutettu näiden avulla. Mitään sellaista laajalti käytettyä kirjastoa ei ole, missä datan sidonnassa voitaisiin käyttää POJOja.

Se, että JSON-data pitää siirtää property-olioihin, ei sinänsä lisää työtä. Vastaava työ olisi myös siirtää JSON-data POJO-olioihin. JSON-POJO-konversio on kuitenkin hallittavissa tavallisimmilla Javan JSON-kirjastoilla suoraan, esim. GSON tukee tätä ja käyttö on ongelmatonta. JSON-Property-konversiota varten ei ole toistaiseksi mitään laajalti käytettyä kirjastoa, ja vaikka sellainen löytyisikin, se olisi ylimääräinen kirjasto vaikkapa GSON-kirjaston lisäksi. Jos konversion joutuu tekemään ylimääräisenä käsityönä, on se tietenkin aina haitta.

Nämä edellä kuvatut ongelmat ovat yleisiä JavaFX:n perusteknologiaan liittyviä, joihin törmää, kun sovellus tehdään JavaFX:n “virallisten” ohjeiden mukaan. Arkkitehtuuriratkaisut joko elävät näiden tilanteiden kanssa, yrittävät niitä vähentää tai jopa poistaa niitä. MVVM-arkkitehtuuri ei ikävä kyllä ainakaan vähennä näitä ongelmia.

Valitettavasti on turha odottaa mitään suurta apua JavaFX:n isännältä, Oraclelta. JavaFX näyttää siirtyneen vaiheeseen, jossa sitä ei enää aktiivisesti kehitetä. Oraclen kiinnostus kohdistuu pääosion web-käyttöliittymiin. Samoin on laita Microsoftin WPF-tekniikan puolella. Web-käyttöliittymät ovat nyt suosion aallonharjalla, desktop puolestaan sivuroolissa.

Miten nämä ongelmat voitaisiin ratkaista? Ratkaisu on suora suhteessa ongelmaan, luovutaan databindauksesta sekä property-luokkien käytöstä. Tämäkään arkkitehtuurinen ratkaisu ei tarvitse mitään ulkopuolisia, eikä muitakaan kirjastoja, kyse on puhtaasta arkkitehtuurista JavaFX:n tukemin keinoin.

Databindauksesta luopuminen tuntuu ensin suurelta takaiskulta. Näin ei kuitenkaan ole, koska sillä on myös suuria positiivisia vaikutuksia, yleisesti ja erityisesti JavaFX:aan liittyen:
– Databindaukseen liittyvä massiivinen boilerplate-koodi häviää kokonaan
– Data on tuttua POJOa ja JSONia
– Sovelluslogiikka tulee käytännössä tilattomaksi, kun “tila” on fxml-elementeissa. Tämä on iso yksinkertaistus.
– Sovelluskoodin yhteismäärä jopa pienenee, boilerplate-koodin vaikutus on niin suuri
– Koodi on yksinkertaisempaa ja ymmärrettävämpää
– Sovellus toiminee jopa nopeammin (databindaus toimii taustalla), tosin eroa ei useimmiten huomaa käytännössä.

Negatiivinen vaikutus tulee databindauksen puuttumisesta. Tämä voidaan kuitenkin rajata kontrolleri-luokkaan, siinä tapahtuu fxml-elementeilta lukeminen ja niihin kirjoittaminen. Kontrollerin tehtävä, datan ja käyttöliittymäelementtien yhteistyön järjestäminen siis pysyy samana, keinot vain vaihtuvat. Käsitys, että databindaus on välttämätöntä, on perusteeton.

Koodin selkeyteen databindauksesta luopumisen vaikutus on siis ristiriitainen, toiset kohdat sovelluksessa yksinkertaistuvat, toiset monimutkaistuvat. Kannattaa huomata, että monimutkaisemmat käyttöliittymätapaukset on aina hoidettavissa suoraan elementeista lukemalla ja kirjoittamalla, mutta mitään takeita ei ole siihen, että taipuuko databindauksen kirjastot vastaavaan.

JavFX application with MVP-architecture

Esimerkiksi textField-kentän käyttäminen ilman databindausta, tuskin kukaan pitää tätä ongelmallisena:
    txtFirstName.setText(firstName); // kirjoittaminen
    String firstName = txtFirstName.getText(); // lukeminen
Myös vaikkapa comboboxin tai taulukon tietojen käsittely on yhtä helppoa.

Kontrollerin käsittelemä datan ja kontrollien yhteistyö ei leviä tässä ratkaisussa sovelluslogiikkaan millään tavalla. Vaikka käytettäisiin asynkronisia serveripään kutsuja, voidaan saadun paluuarvon käsittely siirtää aina kuuntelijarajapinnan kautta kontrollerille. Kyseessä on MVP-arkkitehtuurin mukainen ratkaisu (Model-View-Presenter).

public interface ILogicReceiver
{
    // tähän metodit, jotka tuovat palvelukutsuista saadun datan käyttöliittymään
    public void giveIndex(int inx);
}

Kontrolleri toteuttaa kyseisen rajapinnan. Kontrollerin jäsenmuuttujana on sovelluslogiikkarajapinnan (tässä käytämme rajapintaa, emme perintää, kuten aikaisemmassa blogissamme) toteuttavan luokan olio:

public class HelloController implements ILogicReceiver
{
    private IHelloLogic logic = new HelloLogic(this); // rajapintaoliota!
    // …….
    public void giveIndex(int inx) { txtIndex.setText(inx); }
    public void getIndex() { logic.getIndex(); }
}

Jotta sovelluskutsun paluun kuuntelu onnistuisi, sovelluslogiikan olio ottaa muodostimessa vastaan kyseisen rajapinnan toteuttava olion (edellä “this”). Käytännössä tämä tarkoittaa, että sovelluslogiikalle annetaan kontrollerin olio viittauksena. Näin datan käsittely ei valu sovelluslogiikkaan. Aikanaan databinausta perusteltiin voimakkasti nimenomaan sillä, että suora käyttöliittymäkomponenttien käsittely sotkeentuu sovelluslogiikkaan. MVP-arkkitehtuuria käyttäen näin ei tapahdu. Kysymys on kontrollerin suhteen vaihtokaupasta: kaksisuuntainen datan sidonta vastaan komponenteista luku ja niihin kirjoittaminen.

Sovelluslogiikan kutsut ovat tilattomia, eli tallennettava data ei ole enää siellä vaan käyttöliittymän elementeissa. Tämä suoraviivaistaa logiikkaa huomattavasti. Kun käytetään databindusta, joudutaan miettimään sovelluslogiikkaan muutettaessa, mikä data on sidottua ja mikä ei, ja aiheuttaako niiden käyttö jotain sivuongelmia.

MVP-mallissa tätä ongelmaa ei ole. Tiedot luetaan elementeista, tehdään palvelukutsu ja tiedot lähetetään elementeille. Toki sovelluslogiikassa voi olla edelleen jotain tallennettavaa dataa, mutta se ei ole enää käyttäjälle näytettävää. Missään nimessä dataa ei pidä kahdentaa niin että se olisi sekä käyttöliittymässä ja sovelluslogiikassa, se on suorastaan sovelluksen miinoittamista. Databindaus nimenomaan kahdentaa dataa, mutta se myös piilottaa tämän käyttäjältä, joten siellä “kahdentaminen” ei ole looginen ongelma.

/**
* Rajapinta kuvaa käyttöliittymän tarpeen.
*/
public interface IHelloLogic
{
    public void getIndex(); // void, koska paluuarvo tulee receiver-rajapinnassa
    // tänne kaikki toiminnot, jotka käyttöliittymä tarvitsee
}

/**
* Yksinkertainen logiikka, ilman asynkronista serveripään kutsua.
*/
public class HelloLogic implements IHelloLogic
{
    private ILogicReceiver receiver;
    public HelloLogic(ILogicReceiver listener) { receiver = listener; }

    public void getIndex()
    {
        inx = ….;
        // haetaan data, ja kutsutaan kuuntelijaa
        this.giveIndex(inx);
    }
}

Kun kutsu on asynkroninen, joudutaan myös logiikassa samankaltaiseen ratkaisuun kuin kontrollerin kanssa, mutta sitä ei esitetä tässä. Siinä HelloLogic luokka vastaavasti kuuntelisi rajapinnan kautta serveripään tulosta, ja tulos käsiteltäisiin HelloLogicin kuuntelijametodissa.

Kyseinen arkkitehtuuri on, kuten MVVM-mallikin, erittäin (automaatti)testattava. Kun halutaan testata käyttöliittymän alapuolinen osa mahdollisimman korkealta tasorakenteen kannalta, se onnistuu korvaamalla kontorolleriluokka testiluokalla. Sovelluslogiikkakutsut ovat kontrollerissa suoraan ja testiluokan on myös toteutettava ILogicReceiver-rajapinta.

On korostettava JavaFX:n arkkitehtuuria käsittelevän blogisarjan lopuksi, että toki kaikki yleiset arkkitehtuurimallit, kuten MVC, MVP tai MVVM, ovat aivan kelvollisia. Niiden valintaan liittyy paljon tekijöitä, joita ei käsitellä tässä. Tärkeää on huomata, että mikään malli ei ole selkeästi muita parempi, ja valintaan kannattaa kiinnittää huomiota. Sovellusarkkitehtuuriratkaisut ovat liian vakavia asioita nettihypen ratkaistavaksi.