[ProgElmélet] RESTful rendszer készítése

Rengeteg írás született már, hogyan készítsünk RESTful rendszert. Sajnos, amiket eddig láttam azok 99%-ban nem RESTful API-kat írnak le. Részletesen elemzik, hogyan alakítsuk ki az útvonalakat (URI path), miként nevezzük el az erőforrásainkat, mikor milyen HTTP metódusokat (POST, PUT, DELETE, PATCH, ...) használjunk, de ezek részletkérdések és általában csak a rossz irányba visznek minket.

Szeretnék rendet tenni a fejekben, ezért mindenkit arra kérek, hogy próbáljon nyitottan állni hozzá és semmiképp se vegyék személyes támadásnak, még akkor se, ha eddig Ők a 99%-ba tartoztak, és azt a REST API felfogást vallják. Remélem a cikk végére és egy kis emésztés után, változik a felfogásuk ;-). Ha nem, az se baj!

A következő cikkben megmutatom a helyes utat ...

Hozzászólások

Szerkesztve: 2020. 04. 05., v – 17:59

Alapvetően egy nagyon jó anyag, de észrevettem benne egy szerintem problémás részt.

Ezt írod:

api/login Bejelentkezés erőforrás. PUT-tal (vagy POST-tal, de erről később) elküldhetjük a bejelentkezési adatokat, majd válaszként kapunk egy tokent, amivel azonosítani tudjuk magunkat. Bejelentkezés előtt, ha GET-tel elkérjük a bejelentkezés erőforrást, akkor 404-es Not Found üzenetet kapunk. Ha bejelentkezés után kérjük el, akkor visszakapjuk a bejelentkezés adatait, pl. ki, mikor, melyik gépről, ..., azon adatokat, amiket a bejelentkezett számára publikusnak gondolunk.

Ez ugye így pont nem RESTful, hiszen REST esetén nincs olyan, hogy a kliens identitiásától, sőt állapotától függően megváltozik a reprezentációja az erőforrásnak amit az adott URI reprezentál. Egy URI által azonosított erőforrás minden kliens számára, ha létezik (azaz nem 404), akkor minden kliens számára ugyanaz kell, hogy legyen - pont emiatt lehet, ahogy írtad is, akár közvetítőkön kersztül is gyorsítótárazni az erőforrásokat.

Emiatt én ezt úgy gondolnám RESTful-nak, hogy az api/login erőforrásra egész egyszerűen a GET kérés nem értelmezett, egy POST kérén során létrejön pedig egy api/login/<loginId> erőforrás, ami már tartalmazza az általad is említett adatokat, és a POST /api/login-ra adott válaszban a Location fejlécben szerepel ezen URI. Így a kliens tudja a saját bejelentkezési adatait, és akár ezt más klienssel is megoszthatja (mármint az URI-t). Amit persze csak arra lehet használni, hogy az egyes bejelentkezéseket azonosítsuk, sőt akár meg is szüntessük (ha értelmes rá a DELETE művelet). Viszont te is említetted, hogy munkamenet-kezelés nincs! Azaz nincs semmiféle, csak a szerver által ismert plusz információ, amit az egyes kliensekhez társít, és aszerint szolgálja ki a kéréseket, hogy ki szólította meg. A kéréseket aszerint kell kiszolgálni, hogy mi volt a kérésben, nem aszerint, hogy ki küldte azt. Lehet benne token persze, ami jogosultságokat azonosít (pl. egy JWT token), de az sem kliens identitástól vagy mukamenettől függ, hanem akár megosztható a klienspéldányok között. Ilyen lehet például egy TLS tanúsítvány is, ami a kliensalkalmazást (és nem a klienspéldányt!) azonosítja. Persze észszerű és biztonságra törekvő esetben ezt az információt az egyes klienspéldányok eltitkolják egymás elől.

Köszi az észrevételt!

Szerintem nem kell minden kliens számára ugyanazt visszaadnia egy erőforrásnak, ezt nem olvasom ki sehonnan a REST definíciójából, de ha tudsz idézni olyat, azt szívesen veszem. Az a része igaz, hogy minden kliensnek ugyanazt az információt kell visszaadnia a szervernek, ha ugyanazon adatokat kapja meg. Tehát, ha másik kliensnek is a birtokába jut a token, akkor az is ugyanazt az információt fogja visszakapni, más tokennel más információt fogsz visszakapni.

Minden egyes kérés bármelyik klienstől tartalmazza az összes szükséges információt a kérés kiszolgálásához, és minden állapotot a kliens tárol.

Ez igaz, hiszen minden adatot átad a kliens, az általa tárolt authentikációs tokent is.

A válaszoknak ezért impliciten vagy expliciten tartalmazniuk kell, hogy gyorsítótárazhatóak-e vagy sem.

Tehát nem kell minden lekérésnek gyorsítótárazhatónak lennie, ezt meg kell adni impliciten vagy expliciten. Pl., ha van Authorization header, akkor azt semmiképpen sem gyorsítótárazza publikusan, illetve expliciten is megadhatjuk a válaszban, hogy ne gyorsítótárazza.

Azt mindenképp elfogadom, hogy nem a legjobb példa. Az általad írt megoldás jobb, de az nem igazán jó példának.
Azt hiszem ki is veszem ezt a példát, mert bár a definíció szerint REST, de szellemében valóban nem igazán az.

Szerintem nem kell minden kliens számára ugyanazt visszaadnia egy erőforrásnak, ezt nem olvasom ki sehonnan a REST definíciójából, de ha tudsz idézni olyat, azt szívesen veszem. 

Én Fielding disszertációjából azt olvastam ki, hogy az egyes erőforrások reprezentációi mindig teljesek a REST műveletekben:

Small or medium-grain messages are used for control semantics, but the bulk of application work is accomplished via large-grain messages containing a complete resource representation. 

https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm

Azaz ha hozzáférsz egy erőforráshoz, akkor teljesen hozzáférsz, attól függetlenül, hogy te ki vagy. Azaz nincs olyan, hogy X kliens számára az U című erőforrást mást tartalmaz, mint Y kliens számára. Az más kérdés, hogy nem férhetsz hozzá minden erőforráshoz feltétlenül.

Persze ez implikálja azt is, hogy az, hogy mi egy erőforrás (mi az, ami egy címmel egyedileg azonosítható), az nagyon-nagyon finomhangolt kell legyen, ha a kliensek számára elérhető információkat nagy finomsággal akarjuk szabályozni.

Amivel amúgy itt játszani lehet, az pont nem az erőforrás reprezentálása, hanem a metaadatoké. Az erőforrás mindegyik kliens számára ugyanaz, de a metaadat nem. És itt kap nagyon nagy szerepet a HATEOAS mint metaadat-halmaz.

Például ha egyes mezőket el akarsz rejteni egyes kliensek elől, más mezőket meg meg akarsz mutatni, akkor azt mondod, hogy van az A erőforrásunk, amit a kliens egy komplett reprezentációként megkap. Viszont mellé X kliens megkapja HATEOAS relációként a számára elérhető plusz mezőket tartalmazó B erőforrás URI-ját, míg Y kliens nem kapja meg ezt a relációt (hiszen ő nem navigálhat arra az erőforrásra).

Ezzel lehet RESTful elkülöníteni az egyes kliensek számára elérhető információkat. Nem az egyes erőforrások reprezentációinak megváltoztatásában, hanem abban, hogy mely erőforrásokat teszed a kliensek számára felfedezhetővé a relációk segítségével.

Szerkesztve: 2020. 04. 05., v – 20:28

Hurrá! Végre egy REST leírás, egyetlen sornyi javascript keretrendszer, json és xml felszopás nélkül.

"Maradt még 2 kB-om. Teszek bele egy TCP-IP stacket és egy bootlogót. "

Szerkesztve: 2020. 04. 06., h – 08:42

Elolvastam. Nekem meg nem teljesen tiszta minden reszlet.

Szerintem kellene egy komplett pelda. A shopping cart szerintem nagyon bonyolult (peldanak), es ohatatlanul egy egyszerusitett elnagyolt pelda lesz.

 

En eddig az api-kat verzioztam. /api/v3/order/2376/pay.

Most ez nem restful, helyette linkeket kellene kuldeni az elejen?

 

Egyebkent mi a baj a post-tal? Az se derult ki szamomra...

Saying a programming language is good because it works on all platforms is like saying anal sex is good because it works on all genders....

Köszi a visszajelzést!

Példán én is gondolkodtam, tervezem is. Jó lenne egy már meglévő példa, amit már mások megcsináltak REST API-val.
Szívesen veszek ötleteket, hogy mi legyen a példa. Meglévő példa azért lenne jó, mert összehasonlíthatóvá válik.

Ha nem linkeket küldesz, akkor az nem RESTful a definíció szerint, de a legtöbben így csinálják, és sajnos a nagy cégek is. Így, ha velük kell kommunikálni, akkor kénytelen vagy Te is olyat csinálni.
Szóval az nem RESTful, de azért nem lehet azt mondani, hogy rossz lenne, csak lehetne annál jobban is csinálni.

Igen, nincs kifejtve, hogy mi a rossz a POST-tal, ezt is tervezem majd részletesen belerakni.
Röviden: a PUT az idempotens, a POST nem az.
Emiatt a PUT-tal, ha megfelelően csinálod (ezt is majd kifejtem), akkor tudod biztosítani, hogy a felveendő elem létrejöjjön és csak egy példányban jöjjön létre. A POST-tal ezt nem tudod garantálni.
Akkor lehet gond, ha elküldesz egy üzenetet, hogy vegye fel az új elemet, de nem érkezik válasz (pl. timeout).
Ilyenkor a küldő nem tudja, hogy létrejött-e az erőforrás vagy sem.
PUT esetén újraküldöd, mivel idempotens, ezért nem gond, ha már felvette, nem veszi fel újra, max felülírja ugyanazt (de ez is elkerülhető).
POST esetén ha újraküldöd, akkor ha már felvette, akkor felvesz mellé még egyet. Ha nem küldöd újra, akkor meg azt sem tudod, hogy létrejött-e, ha igen, akkor mi lett az ID-je.
Ezen a problémán POST esetén nem tudsz javítani sehogy sem.

postnal is tudsz olyat csinalni, hogyha mar van egy elem, akkor nem veszed fel, hanem hibauzenettel visszamesz, hogy duplicated....

Idempotenst is leirod parszor, egyszer beleirhatnad mondjuk zarojelben, hogy az az hogy dempotens, hogyha egy hivast ketszer vegrehajtasz (vagy akarhanyszor), akkor ugyanazt az eredmenyt kapod (pl. halozati hiba, vagy turelmetlen felhasznalo, aki a hozzaszolas elkuldese gombot nyomkodja).

 

Millioan csinalnak todo alkalmazast, amolyan hello worldkent. De kb. az osszes az azonositast elhanyagolja....

De ott kb. van minden: uj elem, elemek atrendezese (modositas), torlese, stb.

Saying a programming language is good because it works on all platforms is like saying anal sex is good because it works on all genders....

"postnal is tudsz olyat csinalni, hogyha mar van egy elem, akkor nem veszed fel, hanem hibauzenettel visszamesz, hogy duplicated...."

Sőt, GET-tel is tudsz létrehozni entitásokat, ha akarsz. A technológiai szinten ez nem tilos.

De nem ez a POST meg a GET szemantikája.

A POST szemantikája az többek között, hogy

"For example, POST is used for the following functions (among others):

Creating a new resource that has yet to be identified by the
      origin server;"

Persze általánosságban véve a POST azt mondja, hogy a címzett erőforrás a kapott adatot a saját szematikája szerint dolgoz fel, nem kell, hogy a POST-ból egyáltalán új erőforrás jöjjön létre. Meglévő erőforrás állapota is frissülhet.

Viszont: ha arra használod a POST műveletet, hogy elemet hozzál létre, akkor te honnan tudod két azonos kliens kérésről, hogy ugyanazt az erőforrást akarják létrehozni, vagy két ugyanolyan erőforrást?

Ez ugye erőforrás-identitási kérdés. Ezért szokás azt mondani, hogy mindenki egyszerűbben jár el, ha betartja azt az ökölszabályt, hogy a POST nem idempotens művelet, míg a PUT az.

"Röviden: a PUT az idempotens, a POST nem az."

Ez így nem igaz, csak célszerű így csinálni.

A POST definíciója szerint bármilyen, erőforrás-függő (az erőforrás által adott szemantikát tartalmazó) végrehajtást csinál.

"

The POST method requests that the target resource process the
   representation enclosed in the request according to the resource's
   own specific semantics. "

 

Például lehet arra használni, hogy subresource-ot hozzál létre. De másra is lehet használni, teljesen a HTTP szemantikájának megfelelően. Fielding sem ír a REST kapcsán arról, hogy hogyan kell HTTP metódusokra mappelni a REST műveleteket.

Ez csak egy megszokás, egy ökölszabály, ahogyan a POST-ot erőforrás-létrehozásra érdemes használni, de nem örök érvényű igazság.

"POST esetén ha újraküldöd, akkor ha már felvette, akkor felvesz mellé még egyet. "

Nem feltétlenül, mint lentebb írtam, ez erőforrás-identitási kérdés. Ha két azonos reprezentációt küldesz, akkor azonos erőforrást jelentenek, vagy ugyanolyat? Például egy content-addressable storage esetén két azonos reprezentáció azonos erőforrás lesz. De nem minden storage CAS elvű.

Ez így nem igaz, csak célszerű így csinálni.

Valóban nem teljesen igaz. Az igazság az, hogy a HTTP szabvány ezt leírja, hogy így kellene használni és aki használja (kliens) az úgy gondolja, hogy a szerveren így van megvalósítva.

4.2.2. Idempotent Methods

A request method is considered "idempotent" if the intended effect on the server of multiple identical requests with that method is the same as the effect for a single such request. Of the request methods defined by this specification, PUT, DELETE, and safe request methods are idempotent.

Like the definition of safe, the idempotent property only applies to what has been requested by the user; a server is free to log each request separately, retain a revision control history, or implement other non-idempotent side effects for each idempotent request.

Szóval nem megszokás és nem ököl szabály, hanem ez a szabvány.

A szabványtól persze el lehet térni, de akkor egy csomó problémánk fog akadni, mert a közbenső szerverek is azt feltételezik, hogy a szabvány szerint megy a HTTP kommunikáció.

A szabvány azt mondja itt, hogy mi az, ami biztosan idempotens művelet, és ha nem úgy csinálod, akkor rosszul csinálod.

Viszont ez nem mondja, hogy a POST nem lehet sosem idempotens, hiszen leírja egyértelműen, hogy a POST szemantikája az erőforrásfüggő.

Az, hogy van olyan erőforrás, amire a POST szemantikája idempotens, az erőforrás-specifikus kérdés. Nem mondhatod, hogy a POST mindig idempotens, vagy sosem az, hiszen a szabvány nem írja elő, hogy a POST-nak miként kell működnie, azt írja elő, hogy az erőforrás értelmezi a POST műveletet erőforrás-specifikusan.

Ebben amúgy nem egyértelmű a specifikáció, mert az IANA regisztráció szerint a POST nem safe és nem idempotent, viszont a specifikációja szerint meg ez nem egyértelmű, ugyanis nem írja elő, hogy nem lehet idempotent a működése sosem, hiszen a szemantikáját nem specifikálja. Én úgy értelmezem a szabványt, hogy a POST jelentését és működését minden esetben le kell specifikálni erőforrás-specifikusan. Így aztán az is előfordulhat, hogy a POST safe és idempotent lesz (ritka rá az esély, de a szabvány megengedi).

Az erős, ha azt mondjuk, hogy POST-ot csak úgy implementálhatsz, hogy az non-safe és non-idempotent. 

Az a fő különbség a PUT és a POST között, amitől az egyik idempotens lesz, a másik meg nem az.

Ha létre akarsz hozni egy erőforrást, akkor PUT esetén pontosan megadod az erőforrást az azonosítójával együtt. Így akár hányszor végrehajthatod, egy azonosítóval csak egy erőforrás szerepelhet, így ugyanaz lesz az eredmény.
Ugyanezt POST esetén nem tudod megtenni, mert nem tudod megadni az azonosítóját. Épp ezért használsz POST-ot, mert nem tudod megadni, így tízszer végrehajtva tízszer veszi fel az erőforrást a megadott erőforrás alá, így nem lesz idempotens. Ha meg tudnád adni, akkor a PUT-ot használnád, mert az ugyanazt csinálja, csak idempotensen.

Hasonlóan a módosításnál, ha teljesen felül akarod írni az egész erőforrást egy új példánnyal, akkor az idempotens, mert tízszer felülírva ugyanazzal is az lesz, tehát PUT.
Ha viszont, csak hozzá akarsz adni egy új elemet, akkor az nem lesz idempotens, mert tízszer végrehajtva tíz elemet vesz fel, tehát POST.

Senki nem mondta, hogy a POST-nak erőforrást kell létrehoznia vagy módosítania mindenképpen. A POST szemantikája erőforrásfüggő, ennyit mond a szabvány. Például a POST lehet egy olyan értelmes művelet a  /functions/uppercase erőforráson értelmezve (amely erőforrásra a GET például a függvény forráskódot adja vissza, a DELETE az uppercase függvényt törli, a PUT pedig újra létrehozza a function-t), ami az elküldött JSON üzenetben minden stringet nagybetűsít és ezt adja válaszul, anélkül, hogy bármi történne a szerveren. Teljesen megfelel a REST-nek és a POST szemantikájának a HTTP szabványban. Semmiféle erőforrásnak nem kell létrejönnie POST esetén, ezt semmi nem írja elő.

Mondhatnánk, hogy de erre a GET való, viszont a GET csak URI-val működik (lehet bodyja a GET kérésnek is, de a body nem vehető figyelembe a kérés kiszolgálásakor, csak a request URI).

 

Az más kérdés, hogy ha létre akarunk hozni erőforrásokat, akkor azt megtehetjük POST-tal és akkor tényleg nem safe és nem idempotens. De ezt a szemantikát senki nem mondta, hogy követnie kell a POST metódusnak egy erőforráson. Az, hogy subresource létrehozásra POST-ot használunk, nem jelenti azt, hogy mindig arra kell használni a POST-ot, a szabvány semmi ilyet nem ír elő.

Ebben igazat kell adjak, tényleg lehet safe a POST egy megvalósítása is, nem tiltja a szabvány.

Azonban azt hozzáteszem, hogy ennek ellenére a POST-ra a közbenső kiszolgálók semmikor sem fognak safe-ként tekinteni és elvileg a klienseknek sem szabadna.

Mondjuk ő generálja. Egy UID. Vagy a tartalom valamilyen kriptográfiai hash-e.

Nem kell az azonosítónak mindig szigorúan sorszámozottnak lennie, sőt, bizonyos helyeken biztonsági okból teljesen ellenjavalt. Hiszen egy azonosítót megszerezve próbálgatással kitalálhatók a többi érvényes azonosítók. Ilyenkor pl. az UUID jobb.

Például a munkameneteket se sorszámozottan azonosítod.

Engedjük el azt a dolgot, hogy azonosító = autoincrement mező a DB-ben.

Például a git commit Idt is elő tudja állítani bármelyik gitkliens, és pusholni tudja, attól függetlenül, hogy a másik oldalon mi van.

Ezt a részt is még jobban ki fogom fejteni.

Így van, ahogy percsicsb is írja.
Lehet UUID, vagy HASH, amit a kliens generál, de kaphatja a szervertől is.
Ha úgy csináljuk meg a REST szervert, ahogy írtam, akkor minden linket a szervertől kap, tehát ezt is, amivel újat tud felvenni.

Alapvetően én is amellett vagyok, hogy főként UUID-vel, esetleg HASH-sel azonosítsunk mindent, ez legyen az elsődleges azonosítónk.
Ha kell bele pl. sorszám, akkor az lehet egy másodlagos azonosító.

A resource felvitelere szolgalo link generalasakor minden kliens szamara letrehozna egy sajat azonositot, amivel letre tudja hozni az uj resource-t?

Mi a helyzet, ha tovabbi eroforrast akar letrehozni? A PUT-ra adott valasz tartalmazna a kovetkezo resource letrehozasara hasznalhato linket?

Egyetertek azzal, hogy az ID ne egy autoincrement mezo legyen az adatbazisban. Azt nem tudom, hogy jo otlet-e a kliensre bizni az azonosito generalasat.

Így van, minden kliens, aki újat akar létrehozni, kap egy saját azonosítót.
UUID-t létrehozni nem kerül semmibe, ha nem használják fel, akkor sincs semmi.

Ha úgy akarjuk megcsinálni a felvitelt, hogy egyből egy újat is tudjon létrehozni, támogatva a tömeges felvitelt, akkor igen, a PUT-ra adott válaszban ott lehet az új erőforrás létrehozására szolgáló link.

Igen, általában nem jó semmit se a kliensre bízni, amit a szerver is meg tud csinálni. A hibalehetőségek csökkenthetők.
ID létrehozásra meg különösen igaz.
Ezért is jobb az általam is leírt REST rendszer, mert ez nagyon kevés dolgot hagy meg a kliensnek.

"Igen, általában nem jó semmit se a kliensre bízni, amit a szerver is meg tud csinálni. A hibalehetőségek csökkenthetők."

Viszont az egész REST-nek a lényege, hogy mindent a kliensre bízunk, amit a kliensre lehet bízni.  A szerver a lehető legkevesebb dolgot csinálja, mert ő korlátos erőforrás.

Ez az egész, hogy "nagyon kevés dolgot hagyunk a kliensnek", pont a REST elveivel tökéletesen ellentétesek. Persze, ez azzal is jár, hogy a klienseknek okosnak kell lennie, és az okos kliensek problémát okozhatnak.

Ez ahhoz hasonló, mint amikor egy adatbázist többféle program is kezel, és minden kliensnek, aki hozzáfér az adatbázishoz, jónak kell lennie.

De ez benne is van Fielding disszertációjában, hogy ez egy ismert probléma, de a skálázási problémát ezzel lehet megoldani:

"In addition, placing the application state on the client-side reduces the server's control over consistent application behavior, since the application becomes dependent on the correct implementation of semantics across multiple client versions."

https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm 5.1.3-mas fejezet, utolsó mondat.

Azaz a server az a REST esetén meglehetősen buta, a kliensek azok, akik nagyon sok részt átvállalnak az alkalmazásból. Ők azok, akil a layered architecture miatt akár több szerver több erőforrását fűzik össze egy-egy kliensoldali erőforrássá, amivel már dolgoznak.

Pont ettől lesz skálázható az egész. És pont ezért dolgozik a kliens mindig teljes reprezentációkkal, nem csak azzal az apró kis résszel, amivel neki van dolga. Például nem csak egy usernevet küld el, amikor usert akar szerkeszteni, hanem a teljes user entitást, és ezt is küldi el a szervernek, amikor végzett a szerkesztéssel. Hiszen megtehetnénk, hogy RPC-ként kezeljük az egészet, és minden logikát a szerverre bízunk, hiszen "nem jó semmit a kliensre bízni, amit a szerver is el tud végezni". Ez pont nem RESTful.

Le is írja Fielding, hogy az állapotmentes működés egy design trade-off. Feláldozzuk a "server knows it all and knows it best" mondást a skálázhatóságért.

Azt hiszem félreérted.

Nem minden tevékenységeket veszünk el a klienstől, hanem főleg a "döntéseket".
A skálázódás biztosítására való a kliens-szerver architektúra, az állapotmentesség, a gyorsítótárazhatóság és a réteges felépítés.

Döntések alatt azon dolgokat értem, amik az Egységes interfész részben szerepelnek.

  • Erőforrások azonosítása. A kliensnek nem kell tudnia semmit, hogyan képződnek az azonosítók. Ha rábízzuk a kliensre, azzal csak a hibalehetőségeket bővítjük, de teljesítményben, skálázódásban nem nyerünk semmit.
  • Erőforrások manipulációja ezeken a reprezentációkon keresztül. A szerver biztosít minden adatot, ami a kliensnek kell az erőforrás manipulációjához, nem kell további kéréseket intéznie a szerver vagy más szerverek felé.
  • Önleíró üzenetek. A szerver biztosít minden adatot, ami a kliensnek kell az üzenet feldolgozásához, nem kell további kéréseket intéznie a szerver vagy más szerverek felé.
  • Hipermédia, mint az alkalmazásállapot motorja. Ez a legfontosabb. Idézem is. "A kliensek csakis azokon az állapotokon mehetnek át, amelyeket a szerver által küldött hipermédia tartalmaz hivatkozások alakjában." A szerver küldi a hivatkozásokat, aminek a legfontosabb eleme az erőforrás azonosítója (URI), de része a reláció, a HTTP metódus, a média típus, ... Ezeket mind nem kell kitalálnia a kliensnek, mert megkapja.

A szerver a lehető legkevesebb dolgot csinálja, mert ő korlátos erőforrás.

Alapvetően ezzel sem értek egyet. Egy mobiltelefonhoz vagy esetleg egy IoT eszközhöz képest, mitől lenne egy szerver, ami igény esetén új node-okat indít, korlátos erőforrás? A szerver is általában korlátos erőforrás, ha nem bővülhet kedve szerint, de a kliens az mindig az. Úgy kell elosztani a feladatokat, hogy a kliensek és a szerver(ek) is beleférjenek a korlátaikba.

Szerintem te is félreérted.

Pont arról szól a REST (és a skálázhatóság), hogy a döntések kliens oldalon vannak. Az erőforrások állapotai csak az erőforrás műveletek segítségével változhatnak meg, és ezen műveletekben teljes erőforrás-reprezentációkat küldünk el a szervernek.

REST components perform actions on a resource by using a representation to capture the current or intended state of that resource and transferring that representation between components. 

Azaz a kliens számolja ki, hogy amikor A erőforrás állapota S1-ről S2-re változik, akkor S2-nek (intended state) hogy néz ki a reprezentációja. A kliens tudja, hogy egyáltalán S1-ről milyen S2 állapotokba lehet az erőforrást átküldeni, és utána azt hogyan kell reprezentálni.

A szerveren nagyon kevés "döntés" van, az ő feladata az erőforrások állapotai reprezentációinak kiszolgálása és az állapotátmenetek fogadása a kliensektől.

Az, hogy a " kliensek csakis azokon az állapotokon mehetnek át, amelyeket a szerver által küldött hipermédia tartalmaz hivatkozások alakjában."  totálisan ellentmond annak, amit a REST mond. Ha a szerver számolja ki a jelenlegi állapot alapján az összes lehetséges következő állapotot, annak semmi, de semmi értelme nincs.

Például van egy erőforrásom, ami egy 16 bites, előjel nélküli egész számot tárol, például egy raktárkészlet-mennyiséget. Mindegyik 16 bites előjel nélküli egész szám érvényes állapottér-elem, és bármelyik állapotból bármelyikbe érvényesen el lehet jutni, az egyes állapot-módosulások a raktárkészlet-mozgásokat jelzik. A szerver az összes lehetséges 16 bites egészet, mint új lehetséges állapotot elküldi a kliensnek mint hivatkozást, hogy a kliens válasszon közülük? Ezt te sem gondolhatod komolyan! Nonszensz.

 

 Azt, hogy mi az új állapota az erőforrásnak, azt a kliens számolja ki, ő dönt róla, hogy mi legyen, és neki is tudnia kell az érvényes állapotátmeneteli lehetőségeket. Az előfordulhat, hogy a szerver elutasítja, hogy a kliens egy-egy állapotátmenetet megvalósítson, mert nem teljesülnek feltételek (megváltozott például az erőforrás állapota azóta, hogy a kliens erről értesült volna, vagy éppen az erőforrás már nem létezik, vagy az erőforrás már más címen érhető el, esetleg a kliens már nem jogosult végrehajtani az állapotátmenetet).

A hivatkozások a HATEOAS-ban arra valók, hogy megmutassák, mik a kapcsolt erőforrások (és nem azt, hogy mik a mostani erőforrás esetleges jövőbeni állapotainak a megváltoztatását leíró URI-k). Az URI-kban (hivatkozásokban) sosem az erőforrás konkrét állapotára hivatkozunk, hanem az erőforrásra magára, ami egy adott erőforrás kontextusában még a kliens számára érdekes lehet. De sosem az állapotátmenetekre vonatkoznak a hivatkozások! Az egy teljesen nonszensz dolog.

"kliensek csakis azokon az állapotokon mehetnek át, amelyeket a szerver által küldött hipermédia tartalmaz hivatkozások alakjában.""

Az, hogy mi a kliens állapota, majd a kliens tudja. A kliens tart nyilván minden munkameneti állapotot is. A kliens tartja nyilván, hogy ő még hány más szervertől kért le erőforrás-állapotokat, amikor dolgozott és a szerverektől erőforrás-állapot módosítást kért.

A szervernek semmi köze ahhoz, hogy a kliens milyen állapotban van, és milyen állapotokba mehet, a szerver semmit nem tud a kliens állapotteréről (pont a réteges felépítés miatt). A szerver csak a szerver által kezelt erőforrásokról és azok állapotteréről tud, a kliens állapotteréről semmit nem tételezhet fel (ha bármit feltételezne a kliens állapotteréről, akkor megvalósulna a szerveroldali munkamenet-kezelés, hiszen az pont arról szól, hogy a szerver nyilvántartja, hogy mi az állapota a kliensnek!).

Ez pont a REST-tel homlokegyenest ellentétes dolog lenne, hogy a szerver bármit is korlátozni akarna a kliens állapotai közül. 

A kliens tudja, hogy egy adott erőforrást egy S1 állapotból az S2 állapotba akarja átvinni, hiszen ő (és csakis ő) dönt arról (akár más szerverekről hozott más erőforrások értéke alapján), hogy mi az új állapot. Például a raktáras példánál maradva megnézi, hogy a szállítmányozási rendszerben mi az állapota egy megrendelésnek, és ez alapján módosítja a raktárrendszerben az állapotot.

A szerver sosem számol ki semmiféle erőforrás-állapotot, ez nem dolga neki, sőt, nem is tudja megtenni, hiszen nincs meg hozzá minden információja. Az erőforrások állapota csak a kliensek által módosítható (és az is csak teljes reprezentációk által), és azt csak a kliens tudja, hogy milyen állapotátmenetnek kell történnie.

 

REST esetén az alkalmazás logikája a kliensekben van, a szerverek feladata az erőforrások és azok állapotainak a nyilvántartása, és a kliensektől érkező állapotmódosítási kérések (amelyek mindig teljes reprezentációk!) befogadása vagy elutasítása.

Ez totál hasonlít ám ahhoz (de nem feltétlenül ugyanaz, ezt nem állítom), hogy egy adatbázis is csak egy tárolási réteg, és az alkalmazási logikát az ahhoz kapcsolódó backend (ami ténylegesen az adatbázis kliense) valósítja meg.

Az, hogy a " kliensek csakis azokon az állapotokon mehetnek át, amelyeket a szerver által küldött hipermédia tartalmaz hivatkozások alakjában."  totálisan ellentmond annak, amit a REST mond.

Ez a REST megszorításainak az egyik pontja, a REST definíciójának fontos része (Egységes interfész / Hipermédia, mint az alkalmazásállapot motorja). Ezen tovább én nem is akarok vitatkozni.

Például van egy erőforrásom, ami egy 16 bites, előjel nélküli egész számot tárol, például egy raktárkészlet-mennyiséget. Mindegyik 16 bites előjel nélküli egész szám érvényes állapottér-elem, és bármelyik állapotból bármelyikbe érvényesen el lehet jutni, az egyes állapot-módosulások a raktárkészlet-mozgásokat jelzik. A szerver az összes lehetséges 16 bites egészet, mint új lehetséges állapotot elküldi a kliensnek mint hivatkozást, hogy a kliens válasszon közülük? Ezt te sem gondolhatod komolyan! Nonszensz.

Ez valóban az, de itt nincs is szó a kliens állapotairól. Ennél a példánál a kliens állapotai pl. ezek lehetnek:

  • raktárkészlet-mennyiség megmutatása,
  • foglalás raktárkészletből,
  • raktárkészlet feltöltése.

Ez utóbbi 3 műveletek az erőforrásokon, de nem az erőforrások maguk! Viszont a hipermédia és a REST erőforrásokról szól, erőforrásokat, és nem erőforrásokon elvégezhető műveleteket azonosítanak URI-kal, amire linkelni lehet.

Ez nem RESTful és nem is hypermedia.

Értem én, hogy szerver oldalról akarod korlátozni a kliens által végezhető műveleteket, de ez nem RESTful, a REST nem erről szól. A REST arról szól, hogy a kliens erőforrásokat ismer meg a szerverből, a szerver erőforrásokat tárol, ezen erőforrások pedig csak a kliensek által küldött állapot-transzferek segítségével tudnak megváltozni. A hipermédia meg arról szól, hogy ezeket az erőforrásokat linkeljük egymáshoz, a kliens egy erőforrás metaadataiból más erőforrásokat ismerhet meg.

Az, hogy te az egyes erőforráskon végzett lehetséges műveleteket akarod erőforrásként reprezentálni, az nem lesz RESTful HTTP felett. A RESTfulságnál HTTP felett a műveleteket a HTTP metódusok adják, és az, hogy egy erőforráson milyen metódusokat végezhetsz el, az OPTIONS művelet adja meg.

Persze erre mondhatod, hogy te hát ez így nem jó, hiszen egyes erőforrásokon sokféle művelet végezhető, olyan is, ami nem írható le HTTP-vel. Pedig REST esetén az erőforrások kezelésére egységes interfész van, pont ez a lényeg:

REST connectors provide a generic interface for accessing and manipulating the value set of a resource, regardless of how the membership function is defined or the type of software that is handling the request.

Szóval az erőforrások kezelésére generikus a REST interfész. Az más kérdés, hogy ez korlátozza azt, hogy egy RESTful API-n keresztül mit lehet elvégezni.

 

Te pedig pont ezt at OPTIONS műveletet akarnád kiváltani azzal, hogy erőforrásként azonosítod az erőforráson végezhető műveleteket. Ez nem REST, ez RPC.

Ez utóbbi 3 műveletek az erőforrásokon, de nem az erőforrások maguk!

Nem az erőforrások, persze, hogy nem, hanem a kliens állapotai!
Egy kliensen pl. ez a három képernyő szerepel.
Az, hogy mikor melyikre tudsz átváltani, azt a szerver adja oda neked hivatkozásként.
A belépési pontnál a szerver a raktárkészlet-mennyiség megmutatására ad linket, így a kliens onnan csak oda tud menni.
Ha odamegy, akkor kap két linket, az egyikkel foglalhat, a másikkal feltölthet.
Valamelyiket választja a felhasználó, mondjuk foglal 15-öt, a válaszban egy linket kap a raktárkészlet-mennyiség megmutatására.
Akkor innen csak oda tud lépni (olyan állapotba) a kliens.

Nézd meg ezt az oldalt a HUP-on (mivel a www az REST), az összes linkre kattintva egy új állapotba viszi a kliensedet (böngésző).
Az összes adatot nem adja át a szerver, magát az erőforrás adatait, metaadatait a felhasználó adhatja meg, akár ennek a bejegyzésnek a szövegét is így adtam meg.

De a kliens lehetséges állapotai baromira nem a szervertől és az erőforrásoktól függnek, ezt kéne megérteni!

Például a kliens beszerezhet háromféle erőforrást három szervertől, és majd ő eldönti, hogy mit csinál.

A kliensek állapotterének lehetőségeiről semmit nem tudnak a szerverek. Bárki, aki tud erőforrást valamilyen reprezentációban küldeni meg fogadni a szerver felé, az tud vele RESTful kommunikálni. A szerver semmilyen megkötést nem tehet a kliens állapotára vonatkozóan (mik a "képernyők").

A belépési pontnál a szerver a raktárkészlet-mennyiség megmutatására ad linket, így a kliens onnan csak oda tud menni.

Ha odamegy, akkor kap két linket, az egyikkel foglalhat, a másikkal feltölthet.

Ilyen nincs, ez nem REST! A szerver erőforrásokra, és nem lehetséges kliensállapotokra ad linket, ezt kéne megérteni! A szerver nem tud, és nem kezel semmiféle kliens-állapotteret. A linkek pedig erőforrásokra vonatkoznak, nem pedig erőforrás-műveletekre!

mivel a www az REST

LOL, dehogy REST a www.

az összes linkre kattintva egy új állapotba viszi a kliensedet (böngésző).

NEM nem és nem. A böngésző mint kliens van úgy implementálva, hogy a kapott resource-ból egy új képernyőt rajzol ki, mert ez a humánok elvárása. De a kliensem viselkedhet úgy, mint egy web crawler, viselkedhet úgy, mint egy olyan valami, ami egyszerre nagyon sok helyről fogad információt. Az, hogy a kapott erőforrást hogyan értelmezi a kliens, és milyen állapotba kerül, nem a szerver dönti el! Például egy olyan böngészőlap, amiben fut egy JavaScript alkalmazás, lehet, hogy a szerverrel csak JSON adatokat cserél, és az, hogy milyen állapotot lát a felhasználó, az ettől totál független. Például az egyik szervertől megkapja az időjárás adatokat, a másik szervertől az árfolyam-adatokat, a harmadik szervertől a menetrendi adatokat. És majd a kliens eldönti, hogy ő ezek alapján milyen állapotba kerül, és ez után mit tesz.

Értem én, hogy te azt akarod REST-nek gondolni, hogy a szerver korlátozza, hogy a kliens vele milyen interakciókat tehet meg. De ez sem teljesen REST. Például én intézek hozzád két kérést: lekérem A és B resource-ot. Ekkor milyen állapotban lehet a kliensem, miután ezt a kéréssorozatot lekértem? Csak olyanban, ami B-ből mutat el, vagy olyanban is, ami A-ból is kifelé vezet?

Például A-ból mutat B-be és C-be link, B-ből meg D-be és E-be. Lekérem A-t, lekérem B-t. Ezután (annak ellenére, hogy link B-ből csak D-be és E-be mutat), nyugodtan lekérhetem C-t is, hiszen tudom, hogy az A->C link létezik, gyorsítótáraztam. Szóval attól még, hogy most "a szerver szerint" B az állapotom, a következő kérés mehet C felé gond nélkül, hiszen a szerver ezt mondta, hogy ez érvényes link.

Ha pedig tárolnád szerveroldalon, hogy az A-> C linkbejárás már invalid, mert a kliens az A -> B-t megtette, akkor nem RESTful módon jársz el, hiszen kliensoldali állapotot tárolsz a szerveren.

Te azt mondod, a szervered ismeri a kliens állapotát, ha tudja azt vezérelni a hipermédiával. Pedig dehogy.

Ha van nekem egy REST szolgáltatásom, akkor annyiféle klienst építhetek, amilyet akarok, a szerver nem is tud róla, hogy a kliensemen milyen "képernyők" léteznek, nem kontrollálhatja azt.

Nem akarsz tudomást venni erről az egyszerű tényről, és azt erőlteted, hogy a szerver korlátozni tudja a kliens bármiféle állapotát. Nem, nem tudja. Azt próbálhatja meg korlátozni, hogy a kliens milyen erőforrásokat ismert meg eddig tőled és miket ismerhet meg ezután. De ez teljesen független attól, hogy a kliens milyen állapotban van, és nem is lehet korlátozó tényező.

Legyen mondjuk két kliensem, A és B.

A ellátogat a szerverre, és megismeri A1, A2, ....An erőforrásokat (illetve ezen erőforrások URI-jait). Ezt ő eltárolja magának, hiszen ő kezeli a munkamenetet.

B ugyanígy tesz és megismeri B1, B2, ....Bm erőforrásokat (illetve az erőforrások URI-jait). Ezt ő ugyanúgy eltárolja magának, hiszen ő kezeli a munkamenetet.

És akkor most jön a trükk: attól, hogy A csak az A1, A2...An erőforrásokra ismer tőled linkeket, ugyanúgy megtudhatja például B-től Bm erőforrás URI-ját, ezt te nem tudod kontrollálni. És ekkor ugyanígy ellátogathat a Bm URI-jú erőforrásra, pedig te a hipermédia linkeken keresztül nem mondtad neki, hogy ezek léteznek.

Hiszen a szerver és a kliens közötti kapcsolat állapotmentes, az erőforrások minden kliens felé egyértelműen azonosítottak, nem függ az erőforrás URI-ja a klienstől.

A szerver nem kontrollálja a kliensek állapotát, a kliensek állapot a REST-ben nyugodtan megváltozhat anélkül, hogy a szerverrel kommunikáltak volna, arról a szerver nem tud és nem is tudhat.

 

Hogy értsd: a hipermédia az csak egy lehetséges módja annak, hogy a szerveren lévő erőforrásokat egy kliens megtudja a saját navigációival. De nem az egyetlen lehetséges módja, sőt, REST-ben nem is lehet az - hiszen akkor a szerver megvalósítana munkamenet-kezelést, ha nyilvántartaná, hogy a kliens mely erőforrásokat ismerhette meg tőle és minden más erőforrást, aminek az URI-ját a kliens a hipermádiától eltérő csatornán tudta meg, elutasítja számára. Ez szerveroldali munkamenet-kezelés lenne, ami REST-ben nincs.

Ez ugyanaz, minthogy oké, itt a HUP, van egy csomó link ezen az oldalon, a nyitóoldalról indulva tök mélyre el tudok navigálni, mégis el tudom küldeni ennek a cikknek a linkjét a haveromnak, akinek a kliense meg tudja nyitni a konkrét erőforrást, anélkül, hogy előtte a nyitóoldalra elnavigált volna bárhogyan.

Attól, hogy a REST-ben a szerveren lévő erőforrások URI-jait hipermédia navigációval fel lehet fedezni, még nem jelenti azt, hogy a kliensek csak így tudják megtudni az erőforrásokat, hogy végrehajtják a hipermédia-navigációt linkről linkre. Az, hogy egy adott  kliens milyen erőforrás URI-król tud, ami nálad van, nem függ össze azzal, hogy az az adott kliens milyen erőforrás URI-kat látott már, nem tudod kontrollálni egy adott kliensről, hogy a kliens milyen "képernyőket" ismerhet és milyen állapotátmeneteket kezdeményezhet. Amint egy erőforrás URI kikerült a szerverről valamelyik kliens felé, arról azt kell feltételezni, hogy bármelyik kliens megismerhette. Nem tudod lekorlátozni REST-ben le egy adott kliens által ismert erőforrásokat és a kliens által végezhető állapot-transzfereket arra, hogy az adott kliens milyen hipermédia-navigációt csinált eddig.

Amit itt írtál: " A belépési pontnál a szerver a raktárkészlet-mennyiség megmutatására ad linket, így a kliens onnan csak oda tud menni. "

Ez szimplán nem igaz. A weben is tudok oda navigálni, ahol már más (például egy kereső) már járt, akkor is, ha a belépési pontnál még sosem voltam.

De ez is le van írva Fieldingnél:

In addition to freeing the server from the scalability problems of storing state, this allows the user to directly manipulate the state (e.g., a Web browser's history), anticipate changes to that state (e.g., link maps and prefetching of representations), and jump from one application to another (e.g., bookmarks and URI-entry dialogs).

"A szerver is általában korlátos erőforrás, ha nem bővülhet kedve szerint, de a kliens az mindig az. "

Egy kliens valóban mindig korlátos. De pont attól skálázható az egész, hogy egyszerre több kliensed is lehet, akit bevonsz a feladatba. Pont ettől skálázható a REST - a teljes rendszer állapotátmeneteinek kiszámítását egyszerre több helyen, elosztva végzik, nem pedig egy centrális helyen. A szerver csak segít abban, hogy az egyes kliensek megegyezésre jussanak, hogy mi a rendszer erőforrásainak állapota, tárolja és kiszolgálja a rendszer (vagy a rendszer egy része) erőforrásainak állapotát a klienseknek, de ő maga új állapotot sosem hoz létre és nem számít ki. Hiszen erőforrás-állapot módosulások csak a kliensektől jövő állapot-transzferek segítségével történhetnek.

Hogy az IoT példát mondjam: egy szerver képtelen arra, hogy tízmillió helyről vegyen mondjuk hőmérsékletmintát. De arra már alkalmas, hogy a tízmillió kliens elvégzi a mérést, és ő pedig befogadja a kliensektől az állapotot. Pont erről szól az IoT. Hogy felokosítjuk az eddigi buta eszközeinket, hogy el tudják végezni a klasszikus, nem elosztott szerver munkáját, amennyire csak lehet. Minél több feladatot tolunk a kliensre, mert csak ez skálázható. Erről szól az IoT. A végpont okos, a végpont mint kliens rengeteg mindent megcsinál, amit a szerver nem tud és nem is feladata.

A mobiltelefonnál is - az alkalmazás renderel, ő játszik le videót, ő rajzolja ki a 3D teret, és nem a szerver. A mobiltelefon manipulálja a képet, amikor szerkeszteni kell, a mobiltelefon végzi a hangtömörítést stb. A szerver csak tárolja az állapotokat, de az új erőforrás-állapotokat a kliensek számítják ki. Az okostelefon is pont attól jó, hogy egy csomó mindent meg tud csinálni már, amihez eddig kellett egy központi eszköz, letoljuk most már a feladatot a végpontokhoz.

Szerkesztve: 2020. 04. 08., sze – 17:27

Elkészült a 2. verzió a RESTful leírásból. Hétvégén még írok hozzá, jobban kifejtem a példát is.
Most is szívesen veszem a visszajelzéseket!