2018. március 14.

Mobilbarát API

10 perc olvasási idő

Mobilbarát API

Ennek az írásnak a célja, hogy a backend fejlesztő kollégák kicsit jobban ráláthassanak a mobil fejlesztők igényeire, valamint problémáira, amik egy közös munka során felmerülhetnek. Vaszil Ádám csapattársam egy régebbi posztjában pár pontban már érintőlegesen foglalkozott a backend felé támasztott alapvetőbb mobil fejlesztői igényekkel. Én most bővítem a listát, illetve kicsit részletezem is a felmerülő igényeket, problémákat.

Dokumentáció

Ha egy új API-val kezdünk dolgozni, akkor az első dolog, amivel ezzel kapcsolatban találkozunk, annak dokumentációja. Sajnos már ekkor is több problémával szembesülhetünk. Természetesen ezek a közös munka során, a fejlesztés közben is megjelenhetnek.

Hiányos

A legtöbbször előforduló probléma, hogy hiányos a dokumentáció. Az ugyanezen API-t használó weboldalon (mellesleg általában itt kezdődnek a bajok), esetleg már létező, lecserélni kívánt mobil alkalmazásban látjuk, hogy működik a funkció, viszont a dokumentáció nem tartalmazza a hozzá tartozó hívást. Felmerülhet benned kedves olvasó, hogy ilyen esetben azért vissza lehet fejteni a hívásokat a megfelelő eszközökkel és ez így is van. Viszont nem a mobil fejlesztő dolga ezzel bíbelődni. Hívhatjuk lustaságnak, üzleti érdeknek stb., a hiánytalan dokumentáció minden szereplő érdeke. Az ok általában, hogy a fejlesztő siet, az új funkciókat már várják, levélben, vagy chaten egyeztetett a frontendes kollégával, majd a módosítás kimarad a dokumentációból.

Elavult

A dokumentáció elavultsága is rendszeresen felmerülő probléma. A funkció létezik, a felületes olvasó látja, hogy a doksiban is benne van, majd használatkor szembesül vele a fejlesztő, hogy nem a leírtaknak megfelelően működik. Természetesen ekkor meg lehet keresni a backendes kollégát és egyeztetni, hogy mi a probléma, ki lehet javítani a dokumentációt, majd mentegetőzni az ügyfélnél, hogy miért csúszik a fejlesztés. Persze ez egy kicsit túlzó megfogalmazás és a csúszásoknak általában nem csak ez az oka, de nem baj, ha a fejlesztési időbe beleszámítjátok a fejlesztők közti „felesleges” kommunikációt. Lesz.

Verziózás

Mobil applikáció fejlesztésénél tekintettel kell lennünk arra a tényre, hogy a felhasználók egy része nem frissíti rendszeresen az applikációkat. Ez lehet az operációs rendszer gyári beállításai miatt – tehát napokig nincs wifi hálózatra, esetleg töltőre csatlakozva –, vagy maga a felhasználó kapcsolja ki az automatikus frissítés funkciót. Hogy konkrét számokról is beszéljünk, az egyik applikációnk statisztikái alapján, az első 2 napban az Androidos felhasználók fele frissít, majd a következő két hétben is csupán 85 százalékig jut el a frissítések száma. Nagyjából a felhasználók 10 százaléka le van maradva két verzióval.

Ebből belátható, hogy miért rossz ötlet az éles API-ban olyan módosításokat végezni, ami jobb esetben csak használhatatlanná tesz bizonyos funkciókat az előző verziós appokban, rosszabb esetben pedig összeomlik tőle az alkalmazás. Az ilyen problémák elkerülhetők a verziózott API-val. Hogy ez hogyan oldják meg, az szinte teljesen lényegtelen. A lényeg, hogy legyen verziózás.

Hibakezelés

Nem kell nagy varázslatra gondolni. Három dologra van szükségünk, egy értelmesen megfogalmazott, az alkalmazás nyelvével megegyező nyelvű hibaüzenetre, amit a felhasználó elé lehet tenni szégyenkezés nélkül, egy státuszra, hogy van-e hiba, illetve egy hibakódra. Ez utóbbira azért van szükség, hogy a kliens oldalon adott esetben a megfelelő eseményeket ki tudjuk váltani, hisz nem minden hibát kell egyformán kezelni, illetve megjeleníteni. Házon belül természetesen ezt megkapjuk a saját fejlesztőinktől. Több hiba esetén ezek mehetnek egy JSON tömbbe.

Milyen hibákat lehet elkövetni?

Idegen és magyar nyelvű hibaüzenetek keveredése

Mivel a hibaüzenetek egy részét muszáj megjeleníteni a felhasználónak, elég kellemetlen, amikor bizonyos hibaüzenetek mondjuk angolul jelennek meg, míg az alkalmazás és az üzenetek nagy része magyar.

Magyar nyelvű, de magyartalanul megfogalmazott hibaüzenetek

Többször futottam már olyan hibaüzenetekbe, amit egyszerűen kínos lett volna megjeleníteni a felhasználónak. A megoldás a kliens oldali validálás és végső esetben valami általános, de értelmes hibaüzenet.

Több hiba egy üzenetbe való összefűzése

Ugyancsak a szervertől kapott szöveg megjelenítésekor problémás, főleg ha az előző pontban tárgyalt gonddal párosul.

Hibakód hiánya

Nem megoldhatatlan probléma, de sokat könnyít a dolgunkon a megléte.

Hibajelzés teljes hiánya

Nem egyszer fordult már elő, hogy a hibajelzés abban merült ki, hogy üres válasz jön. Sajnos ilyen is van.

Státusz hiánya

Ez még nem vészes, de azért jobb, ha van. Nyilván a hibaüzenet meglétéből, vagy hiányából és a tartalom meglétéből, vagy hiányából lehet következtetni a sikerességre, de azért könnyebb és kevésbé erőforrás igényes egy boolean-t ellenőrizni és aszerint folytatni a feldolgozást.

Paraméterezés

Ami igazából a paraméterekkel kapcsolatban problémát okoz, ha változik a paraméter neve, tartalmaz dinamikus elemeket, mondjuk id-t.

Az alábbi formátumot egy post hívás paramétereként láttam:

user[192834756][firstname]: Jason

Itt a cél az lenne, hogy a "192834756" id-val rendelkező user "firstname" paraméterét beállítsuk "Jason"-re.

Ezt a formátumot elég szívás bármilyen olyan framework-kel használni, ami nem sima String típusú kulcs-érték párokra épül, hanem esetleg kihasznál olyan nyelvi szinten adott lehetőségeket, mint Javaban az annotációk. Ott bizony a paraméter neve statikus és a dinamikus paraméter nevek kezelése okoz némi fejtörést.

XML helyett JSON

Méret

A JSON formátum egyik legnagyobb előnye, hogy ugyanazt az adatot kisebb helyen tárolja. Ennek alapvető oka az, hogy a tag-ek helyett, csak egy-egy karaktert használ.

JSON (188 karakter):

{
	"addresses":[  
    	{
        	"street": "Hős utca 9",
        	"zip": "1870",
        	"city": "Budapest"
    	},
     	{
        	"street": "Futó utca 37-45",
        	"zip": "1870",
        	"city": "Budapest"
    	},
     	{
        	"street": "Kerepesi út 9",
        	"zip": "1870",
        	"city": "Budapest"
    	}]
}

Ugyan ez XML-ben (306 karakter):

<?xml version="1.0" encoding="UTF-8" ?>
<root>
    <address>
   	 <street>Hős utca 9</street>
   	 <zip>1870</zip>
   	 <city>Budapest</city>
    </address>
    <address>
   	 <street>Futó utca 37-45</street>
   	 <zip>1870</zip>
   	 <city>Budapest</city>
    </address>
    <address>
   	 <street>Kerepesi út 9</street>
   	 <zip>1870</zip>
   	 <city>Budapest</city>
    </address>
</root>

Jól látható, hogy ebben az esetben az XML több mint 50 százalékkal több helyet foglal. Mobil adatkapcsolat esetén, hosszú távon ez sem elhanyagolható tényező.

Sebesség

Nincs túl sok naprakész cikk a témában, de egy 2014-es összehasonlításban az jött ki, hogy bár a JSON file-ok írási sebessége nem sokban különbözik az XML-től, az olvasásuk meglehetősen gyorsabb.

Használat

Bár ez rajtunk kívül valószínűleg senkit nem érdekel, de az API-val való kommunikációra általunk használt library-ben egyszerűen kevesebb munka létrehozni a parsoláshoz az osztályokat JSON esetén.

JSON, de nem mindegy milyen

Attól, hogy JSON formátumban kapjuk a hívás kimenetét, az még lehet számunkra nehezen használható. A következő pontokban azt fogom összeszedni, hogy milyen gyakori hibákkal találkoztam már, ami megkeseríti egy mobil app fejlesztő életét. Ezek többnyire abból fakadnak, hogy a backend belső adatstruktúráját kapjuk vissza JSON-be szerializálva.

Objektumok felesleges „becsomagolása”

Az alább látható kimenet jó példája a problémának. A lényeges információ itt a „book” tömb. Minden más felesleges. Sajnos többször találkoztam ilyen felépítésű API-val. Ilyen helyzetben teljesen feleslegesen kell a parsernek két osztály helyett ötöt írni, ráadásul 1-1 mezővel. Mindezt úgy, hogy semmilyen többlet információval nem bír számunkra.

{
  "library":{
	"shelf":{
  		"books":{
    		"book":[
      			{
        			"id":0,
        			"title":"The art of API design",
        			"author":"Artie Apfel"
      			},
      			{
        			"id":1,
        			"title":"API design for dummies",
        			"author":"Desmond Dumber"
      			},
      			{
        			"id":2,
        			"title":"JSON for PHP developers",
        			"author":"Jason Phonge"
      			}
      		]
   	 
  		}
	}
  }
}

És a megfelelő forma:

{
    "books":[
        {
            "id":0,
            "title":"The art of API design",
            "author":"Artie Apfel"
        },
        {
            "id":1,
            "title":"API design for dummies",
            "author":"Desmond Dumber"
        },
        {
            "id":2,
            "title":"JSON for PHP developers",
            "author":"Jason Phonge"
        }
    ]
}

Tömbök helyett kulcs-érték párok

Ebben az esetben az a probléma, hogy ha egy tömböt várunk, ahol általában az elemek sorrendje számít, akkor az Java-ban egy ArrayList-be, Objective C-ben pedig NSArray típusra lesz parsolva, amelyek sorrendtartó listák. Ellenben ha kulcs-érték párokat kapunk a tömb helyett, akkor azt kénytelenek vagyunk valamilyen Map-be, vagy Objective C esetén NSDictionary-be parsolozni. Ezek az adatstruktúrák viszont nem feltétlenül tartják a kulcsok hozzáadás szerinti sorrendjét. A Gson parser – amit Android esetén használunk – szerencsére LinkedTreeMap-et használ, ami a kulcsok iterálásakor tartja a hozzáadás szerinti sorrendet. Az NSDictionary sajnos nem ilyen, márpedig az NSJSONSerialization abba parsolja a kulcs-érték párokat, így iOS-en vagy újra kell rendezni az egészet, ha van olyan paraméter, ami alapján lehetséges, vagy rendes tömböt kell visszaadnia az API-nak.

Így néz ki a gyakorlatban:

{
    "books":{
        "book_0":{
            "id":0,
            "title":"The art of API design",
            "author":"Artie Apfel"
        },
        "book_1":{
            "id":1,
            "title":"API design for dummies",
            "author":"Desmond Dumber"
        },
        "book_2":{
            "id":2,
            "title":"JSON for PHP developers",
            "author":"Jason Phonge"
        }
    }
}

Sajnos ezzel a problémával is többször találkoztam már. Valószínűleg a PHP azon sajátosságából fakad, hogy az array nem klasszikus tömb, hanem egy rendezett map, ami csak akkor szerializálódik tömbbé, ha az tömbként is van inicializálva kulcsok nélkül.

XML attribútumok a JSON-ben

Ez egy érdekes jelenség és amikor először találkoztam vele nem is nagyon értettem, hogy ez miért van így, és minek bonyolítják vele az adatstruktúrát. Aztán a közelmúltban találkoztam vele újra, egy XML-ből frissen JSON kimenetre váltó API-nál, ahol mindkét dokumentációhoz volt szerencsém. Ez bizony még az XML kimenethez készült, csak úgy maradt.

{
    "books":[
        {
            "@attributes":{
                "id":0
            },
            "title":"The art of API design",
            "author":"Artie Apfel"
        },
        {
            "@attributes":{
                "id":1
            },
            "title":"API design for dummies",
            "author":"Desmond Dumber"
        },
        {
            "@attributes":{
                "id":2
            },
            "title":"JSON for PHP developers",
            "author":"Jason Phonge"
      	}
    ]
}

Néha tömb, néha objektum

Ami ki tudja még nyírni a parsereket, ha egy adott mező értéke több elem esetén tömbként jön, egy elem esetén pedig közvetlenül az adott elemet tartalmazza az adott mező. Ennek a kezelésére vannak megoldások (természetesen pörgeti a munkaórákat), de nem baj, ha az egy elemű tömb is tömbként jön vissza.

Több elem:

{
    "books":[
        {
            "id":0,
            "title":"The art of API design",
            "author":"Artie Apfel"
        },
        {
            "id":1,
            "title":"JSON for PHP developers",
            "author":"Jason Phonge"
        }
    ]
}

Egy elem:

{
    "books":{
        "id":0,
        "title":"The art of API design",
        "author":"Artie Apfel"
    }
}

Hiányzó String helyett üres objektum

Ez az egyik kedvencem. A JSON ismeri a null-t, így ha nincs értéke a mezőnek, akkor legyen null, esetleg be se kerüljön a JSON-be, de üres objektum ne legyen, az eléggé mást jelent.

Az előző példa üres title és hibás hiányzó String kezelés esetén:

{
  "book":{
  	"id":0,
  	"title":{},
  	"author":"Artie Apfel"
	}
}

Helyesen:

{
  "book":{
  	"id":0,
  	"title":null,
  	"author":"Artie Apfel"
	}
}

Végszó

Az említett problémák általában régi appok leváltásakor kerülnek elő, ahol az ügyfél modernizálni szeretné az applikációt, de csak azt. Esetleg egy web applikáció mellé lenne szükség natív mobil appra és a már meglévő API-t kell használnunk.

Ügyfél oldalon nyilván nehezen emészthető, hogy miért is kell az API-hoz is hozzányúlni, illetve minek a mobilnak dedikált API, hisz a felhasználó úgysem látja, ráadásul a meglévő amúgy is évek óta hibátlanul működik.

Fejlesztői oldalon a probléma gyökere az lehet, hogy máshogy gondolkodunk, más megoldások kézenfekvők egy backend, egy frontend, pláne egy mobil fejlesztőnek, ahol egy oldalon relatíve kevés infó jelenik meg egyszerre, illetve akad néhány megkötés, ami a backend fejlesztőknek idegen lehet.

Az API tervezésekor figyelembe kell venni, hogy milyen kliens fogja azt használni, milyen igényei és korlátai vannak. A mobil alkalmazás ilyen szempontból nem túl hálás. Kevésbé tűri a hibákat, legyen az tervezési vagy működésbeli. Ilyen szempontból közelebb áll egy asztali alkalmazáshoz, mint egy webes frontendhez, csak adjuk hozzá az erősen korlátozott számítási kapacitást, a szűkös memóriát és tárhelyet, a korlátozott adatforgalmat, illetve a merülő akksikat.

És hogy mi a megoldás? Mint minden másra az életben.
Figyeljünk oda egymás igényeire és kommunikáljunk.

Tóth Róbert

Mobilalkalmazás fejlesztő. Fő érdeklődési köre a képalkotás, de érdeklődik a jelfeldolgozás egyéb területei iránt is.

Tóth Róbert

Hozzászólások