Representational State Transfer - interface constraints és ajánlott szabványok

Az előző bejegyzésben már említettem, hogy van négy interface constraint:
  1. identification of resources
  2. manipulation of resources through representations
  3. self-descriptive messages
  4. hypermedia as the engine of application state
Ezeket fussuk gyorsan át. Nyilván protokollnak a HTTP-t használjuk, mert ez fekszik legjobban a REST-nek.



Az identification of resources, mint a neve is mutatja azt jelenti, hogy beazonosítjuk az erőforrásokat. Ez egy fokkal műveltebbek, akik az RDF-el már megismerkedtek, azt is tudják, hogy mit jelent az, hogy erőforrás, mert annál is van egy ilyen rész. A resource fogalmának külön története van, amit most inkább nem ismertetnék, mert hosszú. Amit érdemes tudni róla az az URI standard-ben le van írva, az erőforrás valami, amit egy URI-vel egyértelműen azonosítani lehet, lehet ez weboldal (URL), könyvek (URN), egy konkrét személy, cégek (non-dereferenceable IRI), és így tovább.

Ezt bővítette ki az IRI standard azzal, hogy az IRI egy globális azonosító egy erőforrásra, illetve vannak még RFC-k (amiket most nem keresek elő), amik azt írják le, hogy egy konkrét személy vagy dolog hogyan azonosítható non-dereferenceable IRI-vel, pl 301-es azonnali átirányítással egy weblapra, ami az adott thing-et (konkrét való életbeli személyt, céget, tárgyat, stb...) írja le vagy fragment identifier (értsd URI hash) használatával, ami mögött nyilván nincs tartalom, mert nem kérhető le egy HTTP klienssel, nem dereferenceable. Ilyenre példa ha a http://inf3rno-blog.blogspot.hu/inf3rno/person IRI 301-el a https://www.blogger.com/profile/15504572106652681388-re irányítana, ami ugye a profil oldalam a bloggeren. A profil oldal nyilván ilyenkor hivatkozhat a http://inf3rno-blog.blogspot.hu/inf3rno/person IRI-re, amikor rólam beszél, feltéve ha használ valamilyen meta-adat kötő technikát a HTML-ben, pl microdata-t. A fragment identifier-es megoldás ugyanígy működik, csak ott https://www.blogger.com/profile/15504572106652681388#inf3rno amire hivatkozni lehet ilyen meta-adat kötésnél. Így ha egy böngésző megnézi a HTML-t, akkor látja, hogy a profil oldalon egy valós személyre, mint erőforrásra hivatkoznak, aki én vagyok és már előbb említett IRI-k az erőforrás azonosítóim.

A self-descriptive messages-nek pont az a lényege, hogy meta-adatokkal leírjuk, hogy mit tartalmaz egy-egy üzenet, ami a webszolgáltatás és a kliens között megy. Így az gépileg könnyen feldolgozható lesz. Így mondjuk ha microdata-ban megadom valamelyik jól ismert RDF vocabulary, pl a schema.org használatával, hogy
<div itemscope itemtype="http://schema.org/Person">
    <div itemprop="name"><strong>László Lajos Jánszky</strong></div>
</div>
1. példakód - RDF kötése HTML-hez microdata-val - generátor

akkor a google crawler tudni fogja, hogy a HTML-ben egy konkrét személyről van szó, aki én vagyok. Így ha valaki rákeres a nevemre, akkor a google jó eséllyel ki fogja dobni ezt a HTML-t is a találati listán. Nyilván a HTML inkább csak akadály, ha gépi feldolgozhatóságról van szó, ezért szoktak XML vagy JSON formátumokat használni, és az ott megadott adathoz kötni ezeket a meta-adatokat, mondjuk JSON-LD-vel ugyanez:
{
  "@context": {
    "schema": "http://schema.org/",
    "név": "schema:name"
  },
  "@id": "https://www.blogger.com/profile/15504572106652681388#inf3rno",
  "név": "László Lajos Jánszky"
}
2. példakód - RDF kötése JSON-hoz JSON-LD-vel
már gépileg nagyon jól feldolgozható. Így a REST kliens-nek nem fog számítani az, hogy a "név" property alatt éri el a nevemet, mert tol egy expand-et a JSON-LD-re, és máris a "http://schema.org/name" property, ami alatt kell keresnie ezt az információt. Ez nagyon jó olyan szempontból, hogy az implementációs különbségek, pl név, name, teljes név, stb... kiküszöbölhetőek vele. Ezeket a gépek nehezen tudják értelmezni, viszont egy jól ismert vocab-ból származó fogalom (mint http://schema.org/name = személynév), már nem fog gondot okozni nekik.

A REST API-k megvalósításánál általában előszeretettel hanyagolják el ezt a részét a dolognak, és inkább definiálnak helyette egy vendor specific MIME type-ot, így azt mondják, hogy ennek a MIME típusnak a leírása tartalmazza azt az információt (meta-adatot), hogy az üzenet melyik része mit jelent. Ezt nyilván a dokumentációban írják le, aztán a fejlesztők belefejlesztik a kliensekbe, és punktum. Ha az adott kliens nem ismeri az adott típust az adott verzióval, akkor nem tudja feldolgozni az üzenetet, és gyakorlatilag semmilyen információt nem lesz képes kinyerni belőle. Csak egy példa, hogy fogalmatok legyen a dologról. A 2. példakódot írjuk át ilyen formára:
{
  "id": "https://www.blogger.com/profile/15504572106652681388#inf3rno",
  "név": "László Lajos Jánszky"
}
3. példakód - JSON - meta-adat kötés nélkül
Azt hiszem elég világos, hogy ebből egy gépi kliens nem fog tudni semmit sem kibogozni, hacsak nem ismeri az application/vnd.inf3rno.businesscard+json formátumot, amit én erősen kétlek. Ez nem csak azért rossz, mert ilyen módon nem lehet általános klienst (REST böngészőt) írni, hanem mert sokszor egy-egy verzióváltásnál, amikor bővítik a JSON-t, esetleg kicserélik a "név" property-t "name"-re, akkor a régi kliensek azonnal eltörnek. Hozzá szokták csapni a verziószámot a típushoz, pl application/vnd.github.v3+json és accept header-el külön ezt a típust kérik. Így a régi klienseket is ki tudják szolgálni régi verziós dokumentumokkal. Ez ideig-óráig működik, ha menet közben nem változik annyit a projekt, hogy már képtelenek visszafele kompatibilisek lenni. Az RDF kötéses megoldás ennél sokkal rugalmasabb, és elképzelhető, hogy még ilyen esetekben is kitart. Az új major verziónál pedig sokkal célszerűbb egy új root URI-t, pl https://example.com/api/v2/ bevezetni. Így a régi klienseket ugyanúgy lehet szupportálni amíg a logika nem változik annyit, a minor API verzió váltásokkal meg nem kell törődni amíg ugyanazokat a meta-adatokat használjuk, mint a régi klienseknél. Ha most szeretnénk elkezdeni foglalkozni a REST-el, akkor a MIME type-os megoldást tudom javasolni egészen addig, amíg a Hydra RDF vocabulary nem lesz végre szabvány, utána viszont érdemes azonnal váltani olyan további előnyök miatt is, mint pl az API dokumentáció generálása JSON-LD context-ből.

A manipulation of resources through representations lényege, hogy az erőforrások reprezentációját küldözgetjük az üzenetekben, és ezt használjuk fel állapot változtatásra a kliensben vagy éppen a webszolgáltatásnál. Pl egy GET kérésnél a webservice elkéri az adatbázisból az erőforrásra vonatkozó adatokat, becsomagolja JSON-LD-be, aztán odaadja a kliensnek, aki így tisztában lesz azzal, hogy a legutóbbi kérés küldésekor mi volt az erőforrás állapota a webszolgáltatásnál. Ezt a reprezentációt a kliens fel tudja használni arra, hogy megváltoztassa a saját állapotát, mondjuk a felhasználós példánál maradva kiírja HTML-be javascript-el a felhasználó nevét, ha böngészős kliensről van szó, vagy éppen lementi a saját adatbázisába a felhasználó nevét, ha 3rd party (harmadik fél által fejlesztett, pl facebook API-nál a zynga által fejlesztett farmville, ami elkéri az email címet, hogy azonosítson) kliensről van szó. Ugyanígy, ha a kliens állapota megváltozik, pl kitöltenek egy név változtató űrlapot, az adatokat becsomagolják JSON-LD-be, és egy PUT-al vagy egy PATCH-el elküldik a webszolgáltatásnak, akkor a webszolgáltatás validálja az adatokat, aztán lementi a változtatásokat az adatbázisba, ezzel megváltozik a webszolgáltatás állapota, erről küld egy értesítőt a kliensnek egy 200-as status header formájában, hogy minden rendben zajlott. A kliens állapotát application state-nek hívják, a webszolgáltatás állapotát resource state-nek, az állapot cserét a kettő között pedig representational state trasfer-nek. Arról, hogy melyik HTTP method és status header mire való a HTTP standard-ben és annak kiegészítéseiben lehet olvasni. (Sokan vannak, akik webfejlesztéssel foglalkoznak, de még ezt sem ismerik.)

A hypermedia as the engine of application state megkötés a legizgalmasabb a négy közül. Azt mondja ki, hogy minden esetben a webszolgáltatásnak kell megmondani, hogy milyen állapotváltozások lehetségesek. A kliens ezek közül választhat az ezekre vonatkozó meta-adatokat felhasználva, nem saját kútfőből kell találgatnia, hogy vajon melyik HTTP method-ot lehet használni egy erőforrásra, vagy hogy hogyan építse fel az URI-t, vagy éppen mi legyen a body-ban. Mindezt a webszolgáltatásnak le kell írnia a számára valamilyen standard formában. A gyakorlatban ez úgy működik, hogy a webszolgáltatás a kliensnek olyan reprezentációkat ad, amikben hyperlink-ek vannak. Ezeket követve a kliens elkérheti a webszolgáltatás állapotát, vagy neadjisten elküldheti a saját állapotát a webszolgáltatásnak. A hyperlink-ek közül a kliens a hozzájuk csatolt meta-adatok alapján választ, ilyenek pl a link relation-ök, de természetesen mi magunk is kitalálhatunk saját leírókat. Így a kliens és a webszolgáltatás között laza csatolás jön létre, a kliens úgymond függetleníti magát a webszolgáltatás implementációjától pl konkrét URI struktúrától egy-egy erőforrásnál, stb... Ez már ténylegesen olyan, mint amikor objektum orientált programozásnál egy interface-t (uniform interface) definiálunk, amit egy osztály (REST API megvalósítás) implementál. A uniform interface-t tehát szabványokkal és szabvány meta-adatokkal (pl linked data), vagy - ha hosszas keresés után nem találunk ilyet, akkor - saját, alkalmazás specifikus meta-adatokkal írjuk le.

Az általam ajánlott szabványok REST-hez tehát a következőek:
  • HTTP (methods, headers: cache, content-type, accept, status, authorization),
  • URI (IRI, web of things)
  • RDF (XML/ATOM + microdata or RDFa, JSON-LD)
  • meta-data vocabularies (linked open vocabularies, pl schema.org, hydra)
Ezen kívül ajánlom az OAuth2-t, amivel meg lehet oldani 3rd party kliensek jogosultság kezelését is biztonságosan stateless módon. Szerintem a jelenlegi technológia mellett (tehát hogy nincs kész általános REST megoldás és vendor specific MIME type-ot használunk), nem érdemes REST service-eket fejleszteni abban az esetben, ha nem lesznek 3rd party klienseink. Egyszerűen nincs értelme a plusz munkának, amit ráfordítunk. Akkor már inkább SOAP, vagy plain JSON megoldás, ami szóba jöhet. A másik lehetőség, hogy használjuk a hydra-t jelenlegi állapotában, vagy összeszórunk egy saját, nem standard REST vocab-ot a témára, aztán fejlesztés közben kiderül, hogy mennyire használható. Sokan nem értik, hogy kísérleti technológiáról van szó, ami még nem kiforrott, és mindenhol ezt akarják használni, ha megéri, ha nem. Remélem a bejegyzéseim végigolvasása után ti nem estek ugyanebbe a hibába.

Nincsenek megjegyzések:

Megjegyzés küldése