Representational State Transfer - alapvető ismeretek

Sokszor olvasom neten, hogy valaki REST APIt vagy RESTful webservice-t csinál, mert ez az újabb hype már egy pár éve a SOAP után. Nálunk ez itthon annyira még nem gyűrűzött be, mert kicsi a magyar piac, és trendek terén néhány évvel el van maradva a nyugattól.

Én még 2012-ben kezdtem el ismerkedni a REST-el. Beleestem abba a hibába, hogy online tutorial-okból próbáltam megtanulni, illetve stackoverflow-os kérdésekre adott válaszokból. Kellett pár hónap, mire rájöttem, hogy ez nem járható út, mert ezeknek az írásoknak szinte 100%-a teljesen használhatatlan, a készítőik pedig valószínűleg egyformán képzetlenek ezen a téren és vakok közt a félszemű a király alapon egymást oktatják. Talán a legfontosabb pillanat volt ezzel kapcsolatban, amikor valaki ajánlotta, hogy olvassam el a Fielding disszertációt - amit Roy Thomas Fielding írt még 2000-ben - mert súlyos tévedéseim vannak REST-el kapcsolatban. Persze nem hittem neki, hiszen mindent tudtam már a tutorial-okból, dehát azért nekikezdtem, aztán egyik döbbenetből estem a másikba.



A leggyakoribb hiba REST-nél, hogy úgy gondolnak rá, hogy HTTP method-okat használunk CRUD-ra, meg az URI-ket resource-okhoz mappeljük, aztán kalap. Kész a REST service. Hát a valóságban az a helyzet, hogy ennél egy sokkal bonyolultabb dologról van szó. Nem csodálom, hogy a Fielding disszertációt nagyon kevesen olvassák, mert egy száraz szakmai anyag, viszont alapvető, ha érteni szeretnénk, hogy miről szól a REST. Sajnos ez az információ máshol nem nagyon elérhető, bár talán az utóbbi években változott a helyzet, és ehhez talán én is hozzátettem valamit a stackoverflow-os válaszaimmal.

A REST (representation state transfer) durván arról szól, hogy hogyan kellene egy webalkalmazást felépítenünk. Milyen megkötéseket használjunk ahhoz, hogy jól skálázható legyen a weben, milyen adatformátumot használjunk ahhoz, hogy laza csatolást alakítsunk ki a kliens és a webszolgáltatás között. És itt most a kliens a legtöbb esetben nem a böngészőt jelenti, hanem egy olyan alkalmazást, ami használja a webszolgáltatást, tehát HTTP kéréseket küld a REST service felé és feldolgozza a válaszokat. A kliens futhat a böngészőben, de ez nem megkötés, sőt a legtöbb kliens egy másik szerveren fut. Ilyenek például a facebook alkalmazások, amik a facebook API-ra csatlakoznak fel.

Fielding egy csomó dolgot leír a disszertációjában. Szerintem a legfontosabb része a dolognak, hogy a hagyományos kliens-szerver architektúrához képest milyen további megkötések vannak egy REST service esetében, amiket itt lehet átfutni.
  1. stateless constraint
  2. cache constraint
  3. uniform interface constraint
  4. layered system constraint
  5. code on demand constraint
Itt a cache és a code on demand megkötések annyira nem izgalmasak. Az első kb. arról szól, hogy használjuk a HTTP cache-t, és jelöljük meg, hogy melyik tartalom kesselhető és melyik nem. Nem nagy újdonság, legtöbben eddig is így csinálták. A másik arról szól, hogy a kliens opcionálisan beszúrhat kiegészítő kódot, amit a webservice-től kér el. Ez lehet mondjuk egy parser az adatátviteli formátumhoz, vagy bármi egyéb. Ettől sem esünk hasra, nem egy meglepő dolog, bár a legtöbb webservice nem használja ki ezt a lehetőséget, hogy megkönnyítse a kliensek írását. Ők tudják... A maradék, ami igazán érdekes, és amivel részletesebben foglalkozom.
 
A stateless megkötés lényege az, hogy a kommunikáció a kliens és a webszolgáltatás között állapotmentes. Tehát a webszolgáltatás nem tud semmit a kliens állapotáról, és az erre vonatkozó összes releváns adatot minden egyes kérésnél el kell küldenie a kliensnek. Ez durván azt jelenti, ha eddig nem jött volna át, hogy nincs szerver oldali session, session cookie, beléptetés, kiléptetés, semmi ilyesmi, az erre vonatkozó adatokat mindig a kliens kell, hogy tárolja. Skálázhatóság szempontjából ez iszonyatosan fontos a nagy terhelésű oldalaknál, mert szűk keresztmetszet. Tegyük fel, hogy beléptetünk egy felhasználót, adunk neki session id-t, minden fisz-faszt, és az egyetlen dolog, amit letárolunk szerver oldalon az a user id, ami egymilliárd felhasználóra legyen mondjuk egy 32 bites unsigned integer, csak mert miért ne. 1 bájt 8 bit, tehát 4 bájton elfér ez az adat. A session id-re 32 bájtot ajánlanak, de 16 bájtra már azt mondják, hogy tűrhető, úgyhogy legyen annyi, ne essünk túlzásokba. Ez összesen 20 bájt, ami 100 millió párhuzamosan belépett felhasználónál körülbelül 2 gigabájt adat csak arra, hogy be tudjuk azonosítani a felhasználót. Ekkora terhelésnél szinte minden szét van osztva párhuzamosítva külön gépekre egy vagy több szerverparkon a világban. Ezek között megosztani több GB adatot és folyamatosan szinkronban tartani gyakorlatilag másodpercre pontosan nem kis feladat, és nem is szoktak megpróbálkozni vele. Helyette azt mondják, hogy jobb az úgy, ha magában a kérésben minden benne van, amire szükség van a felhasználó azonosításához (pl felhasználói név, jelszó), aztán majd az instance, ami megkapja a kérést a világ valamelyik pontján ez alapján kitalálja, hogy kiről van szó. Nyilván ehhez minden kérést be kell léptetni, de ezt a fajta ritkán változó adatot, mint felhasználói név és jelszó hash, már sokkal könnyebb kezelni, illetve maga a beléptetés kesselhető is, tehát sebességben a hagyományos megoldásokkal (szerver oldali session) megegyező, illetve nagyobb terheléseknél jóval gyorsabb lesz ez a része a webszolgáltatásnak.

A layered system megkötés lényege az, hogy REST service-ekből egy rétegezett elrendezést csinálunk. Minden réteg csak az alatta lévő réteget ismeri. Hasonló ez, mint az absztrakciós szintek, ott sem jó, ha egy osztály több absztrakciós szint mélységig lelát, mint azt már említettem az SRP kitárgyalásánál. A skálázhatóság így még tovább növelhető, mert bármikor áttehetünk egy réteget egy külön gépre, illetve adhatunk olyan feladatokat is az egyes rétegeknek, mint a kesselés. A lentebb lévő szintek semmit nem fognak érezni még egy réteg beiktatásából a rendszerbe, ugyanúgy, ahogy egy objektum orientált program írásánál az alacsony szintű kódra nem lesz semmilyen hatással, ha hozzányúlunk a magas absztrakciós szintű kódhoz, és esetleg még egy szintet kialakítunk. Ezt a megkötést könnyű elhanyagolni, ha a uniform interface-en átesünk, és elfáradunk a végére, ezért hoztam inkább előrébb és hagytam a végére a uniform interface megkötést.

A uniform interface-t úgy tudnám a legérthetőbben megfogalmazni, hogy olyasmi ez, mint amikor kitalálunk egy interface-t, aztán írunk egy osztályt, ami implementálja azt. Itt is definiálunk egy uniform interface-t, ami elrejti a konkrét megvalósítást, annyi a különbség mindössze, hogy ez jóval nagyobb méretekben történik, így az eszközeink is mások. A programnyelv által biztosított interface kulcsszó helyett standard megoldásokat használunk fel ahhoz, hogy leírjuk ezt az interface-t. Ilyen standard megoldások az IP és a HTTP standard, az URI standard, a HTML standard, az RDF standard, standard RDF vocabulary-k, és így tovább. Nincs megkötés arra, hogy mit használhatunk, csak az, hogy standard megoldás legyen. Szóval akár SOAP-on keresztül is használhatjuk a REST-et, nem muszáj a HTTP protokollt használni hozzá. Amit el lehet felejteni ezzel kapcsolatban, hogy kérés-válasz alapú dologról van szó, tehát pl egy WebSocket-en keresztül nem fog működni a dolog, mert az esemény alapú. Nyilván meg lehet próbálni áttunnelezni rajta, de valljuk be, nem erre lett kitalálva. A szabványok használatával kapcsolataban vannak további, nem nyilvánvaló megkötések, ezt négy interface constraint-be szedte össze Fielding:
  1. identification of resources
  2. manipulation of resources through representations
  3. self-descriptive messages
  4. hypermedia as the engine of application state
Ezeket a már említett technikákon kívül természetesen másokkal is meg tudjuk oldani. Én általános standard megoldást személy szerint még nem láttam. Jelenleg amiről tudok, hogy közel áll hozzá, az a Markus Lanthaler által vezetett Hydra W3C Community Group. Egyedi megoldások természetesen vannak, de ezekre nem lehet általános REST böngészőt írni. Ez kb azt jelentené, hogy egyetlen kliens nagyjából ugyanazzal a kóddal tudná használni a facebook API-t, a google API-t és a github API-t is, mert mindegyik egységes felületet (uniform interface) alkalmazna. Ugyanez történik ma a weben, amikor egyetlen böngészővel meg tudjuk nézni az összes weboldalt. Annyi a különbség, hogy a REST-et alapvetően gépeknek találták ki, és a szemantikus web, illetve nagy valószínűséggel a mesterséges intelligencia egy megvalósítása elérhető vele, ha sok kicsi REST webservice-ben írjuk meg annak egyes részeit, és szétskálázzuk az egész webre. A valóság sajnos ennél jelenleg sokkal lehangolóbb, és még nagyon a kezdetén vagyunk a munkának. Az interface megkötésekről és konkrét standard készletekről egy későbbi bejegyzésben írok majd.

Nincsenek megjegyzések:

Megjegyzés küldése