REST service & client uri milyen legyen?
Sziasztok!
Szerintetek egy REST service-nél, ha single page kliens van, és pushState-el frissítem az uri-t, akkor annak milyennek kellene lennie?
Egyelőre az van eldöntve, hogy a kliens és a service külön erőforrás alatt (külön mappában vagy subdomain-en) lesz. Amin most agyalok, hogy a kliens erőforrásai és a service erőforrásai tökéletes tükörképek legyenek e, vagy sem. Illetve, hogy milyen mélységben érdemes a kliens állapotát az url-ben jelölni.
pl:
service - felhasználói adatok:
client - profil oldal:
Szerintetek melyik a helyes?
A users/inf3rno teljes szinkronban van a service-es url-el, de nem jelöli, hogy a felhasználónak a profil oldalán vagyunk. Lehetnénk akár a felhasználói adatok szerkesztésének az űrlapján is, vagy bárhol máshol. Rosszabb helyeken összemossák a kliens-t a szerverrel ebben az esetben, és beteszik a service-be egy külön erőforrásba, mint én az utolsó példánál.
Szerintetek szükség van arra, hogy minden olyan aloldal külön url bejegyzést kapjon, ami szorosan kapcsolódik egy erőforráshoz? Pl: profil oldal / adatok szerkesztése oldal - mindkettő ugyanarra az erőforrásra vonatkozik... Vagy felhasználók listázása különböző limittel és rendezési móddal...
Ha szükség van minden egyes aloldal jelölésére az url-ben, akkor szerintetek a fentiek közül melyik a helyes forma? Muszáj megtartani a service eredeti struktúráját, vagy jobb eltérni tőle, és ha igen, akkor milyen mértékben?
■ Szerintetek egy REST service-nél, ha single page kliens van, és pushState-el frissítem az uri-t, akkor annak milyennek kellene lennie?
Egyelőre az van eldöntve, hogy a kliens és a service külön erőforrás alatt (külön mappában vagy subdomain-en) lesz. Amin most agyalok, hogy a kliens erőforrásai és a service erőforrásai tökéletes tükörképek legyenek e, vagy sem. Illetve, hogy milyen mélységben érdemes a kliens állapotát az url-ben jelölni.
pl:
service - felhasználói adatok:
GET service/users/inf3rno
client - profil oldal:
GET client/users/inf3rno
GET client/users/inf3rno?profile
GET client/users/inf3rno/profile
GET client/users/profile?name=inf3rno
GET client/profile?name=inf3rno
GET client/profile/inf3rno
GET service/users/inf3rno/profile
Szerintetek melyik a helyes?
A users/inf3rno teljes szinkronban van a service-es url-el, de nem jelöli, hogy a felhasználónak a profil oldalán vagyunk. Lehetnénk akár a felhasználói adatok szerkesztésének az űrlapján is, vagy bárhol máshol. Rosszabb helyeken összemossák a kliens-t a szerverrel ebben az esetben, és beteszik a service-be egy külön erőforrásba, mint én az utolsó példánál.
Szerintetek szükség van arra, hogy minden olyan aloldal külön url bejegyzést kapjon, ami szorosan kapcsolódik egy erőforráshoz? Pl: profil oldal / adatok szerkesztése oldal - mindkettő ugyanarra az erőforrásra vonatkozik... Vagy felhasználók listázása különböző limittel és rendezési móddal...
Ha szükség van minden egyes aloldal jelölésére az url-ben, akkor szerintetek a fentiek közül melyik a helyes forma? Muszáj megtartani a service eredeti struktúráját, vagy jobb eltérni tőle, és ha igen, akkor milyen mértékben?
Azt hiszem ez ugyanaz a
Jelenleg én úgy állok, hogy az összes aloldalnak meg kell jelennie valamilyen formában az url-ben. Az ilyen aloldalak mind csak GET-es erőforrások. A client-hez tartoznak szorosan, de kapcsolatban vannak a service-ben lévő erőforrásokkal is. Ezeknek kellene valahogyan megjelenni az url-ben.
A sort és a limit egyértelműen queryString után kell, hogy jöjjön, ez legalább letisztult közben. Hash-t nem lehet használni, hogy kompatibilis legyen a pushState nélküli változattal. Header-t sem lehet használni, mert nem az url része, ahogy külön method-ot sem, szóval azok kiestek. A queryString sem jó szerintem, mert az erőforrás szűrésre, rendszerezésre, stb jó, itt viszont az erőforrás, amihez kapcsolt a dolog nem változik.
Így ez a három jelölt maradt:
GET client/users/profile?name=inf3rno
Az elsővel az a baj, hogy a rest service szerinti megközelítés az egyre szűkíti az adatokat:
users/inf3rno : inf3rno nevű felhasználó
users/inf3rno/email : inf3rno nevű felhasználó email címe
users/inf3rno/email/host : email szolgáltató
....
a profile nem szűkít adatot, sőt nincs is ilyen tulajdonsága az felhasználóknak, tehát nem stimmel.
Ezzel a logikával viszont bejött két újabb:
GET client/profiles/inf3rno
Ezeknél ugyanaz a helyzet, mint az előzőnél: az inf3rno-nak semmi köze nincs a profil oldal tulajdonságaihoz, mert adat azonosító.
Szerintem ezek a megoldások elfogadhatóak (a saját REST-es elképzeléseimmel):
GET client/users/inf3rno?page=profile
GET service/users/inf3rno?page=profile
Az első változat, ami szerintem a helyes, mert hasonló a profile-userData és a users-sort viszonya. A másik két megoldás, ami inkább megszokott, de mégsem keveredik bennük a path-ban az erőforrás és a pluszban felhasznált adat.
Jelenleg olyan szerencsés helyzetben vagyok, hogy az url kinézete nem számít, úgyhogy az első változatot fogom alkalmazni. Ha számítana, akkor meg a megszokott lenne egy kis módosítással:
Az hiszem olyan helyzetben, ahol több erőforrást jelenítünk meg egyszerre, mindig választani kell egy fő erőforrást, amit beteszünk a path-ba, a mellék erőforrásokat pedig betehetjük nyugodtan a queryString-be.
Mint most kiderült, alapvetően a kérdés két erőforrás egyszerre történő megjelenítésére vonatkozott:
service/users/inf3rno
Az egyik az adat szolgáltató, a másik a megjelenítő, és ha history-ba akarunk menteni egy állapotot, akkor mindegyiket kénytelenek vagyunk az url-be tenni.
Ezek alapján a legmegfelelőbb leírási forma:
Csak kibogoztam :D
Ismét sikerült megválaszolnom a saját kérdésem :D
Amúgy érdekel, hogy ti mit gondoltok a témáról, sokan a múltkor sem értették, hogy mit szöszölök ennyit az url-eken :D
Egyébként ezért nincs szerintem még REST szabvány, mert ilyen ellentmondások vannak a rendszerben. Én amit eddig REST-ről láttam, abban az volt, hogy path - erőforrás, queryString - kérés leírása (sort, limit, keresőkifejezés, stb...), header - megjelenítési mód, pl accept header-ben az elvárt mime típus küldése. Ennél a history-s problémánál viszont csak az url-t lehet felhasználni, és a megjelenítési módot is abban kell átküldeni, nem pedig header-ben, úgyhogy ez a változat is bukott egészen addig, amíg nem építik be a böngészőkbe, hogy a history bizonyos az aktuális állapottal kapcsolatos header információkat is letároljon. Szerintem még jó darabig nem lesz ilyen, úgyhogy REST szabvány sem, vagy ami lesz, ott biztos, hogy az url valamelyik részét egynél több dologra fogják felhasználni.
Közben találtam egy mégjobb
GET user/1?calls=update
UPDATE user/1
DELETE user/1
GET user
GET user?calls=create
CREATE user
Általában egy UPDATE-hez vagy CREATE-hez csak egy bizonyos típusú űrlap küldi az információt, illetve egy erőforrást csak egyféle sablonnal jelenítünk meg (ha leszámítjuk az accept header-hez kapcsolt dolgokat, vagy a munkamenetben tárolt skin-t). Szerintem ez az esetek 99%-ában működni fog, úgyhogy inkább ezt használom.
Tovább tákolva ezen, lehet reserved word-öt is csinálni:
GET user/1/UPDATE
UPDATE user/1
DELETE user/1
GET user
GET user/CREATE
CREATE user
Pontosítanál egy kicsit?
A probléma felvetést én nem értem. Pontosan mit értesz az alatt, hogy a kliens erőforrásait és a service erőforrásait különválasztod? Adott két url, ezekre milyen választ vársz, és ki hívja meg őket?
GET client/users/inf3rno
A 2. hozzászólásodat nehezen értelmezem, a következő kérés nekem nagyon idegen:
Ez pontosan mit is csinál? :)
A probléma felvetést én nem
Egyáltalán miért választod őket külön URL-re? Miért nem lehet azonos URL-en kiszolgálni őket más
Accept
fejléccel?Elég szimpla, a service csak
A calls=update az calls=put akart lenni leginkább. Kb annyit jelöl, hogy űrlapról van szó, amivel PUT kérést lehet végrehajtani. Ilyen űrlap meg általában csak egy szokott lenni egy erőforráshoz, azért használható ez a megközelítés az esetek döntő többségében. További előnye, hogyha kliens oldalra lemásolod, hogy mire van joga a felhasználónak, akkor az ilyen típusú linkeknél kapásból meg tudod nézni az erőforrást, és azt, hogy put-ot akar használni, és kiírod, hogy nincs joga a művelethez. Szóval már eleve az űrlap sem fog lejönni. A calls helyett uses vagy sends vagy method, vagy valami ilyesmi kifejezés kellene, maga a calls az szerintem nem a legjobb szó, de még nem találtam meg az igazit.
Visszatérve, az alap probléma elég egyszerű, arról van szó, hogy van egy adatforrásod és van több megjelenítőd, ami ugyanazt az adatforrást használja. Hogy írod le ezt url-ben úgy, hogy a path részben csak az adatforrásod szerepelhet? Nyilván a queryString-be fog menni minden más...
Még valaki közületek belinkelt egy video-t a REST-ről, ami alapján próbáltam elindulni:
http://munich2012.drupal.org/program/sessions/designing-http-interfaces-and-restful-web-services
Sajnos a benne lévő állítások, bár szépek, de nem lehet rájuk alapozni, mert az aktuális oldalt csak url-el lehet leírni, és az aktuális oldal megjelenítése (amit ők header-be raknának) is az url-be kell, hogy kerüljön.
Ezt az egész ellentmondást egyébként fel lehet oldani úgy is, hogy az adatszolgáltató egy REST service, ami megfelel az ott leírt elveknek (path - erőforrás, queryString - lekérdezés módosítók, header - megjelenítés, method - mit csinálunk), a hozzá tartozó kliens az url-eket tekintve meg egy hagyományos webalkalmazás. Szóval ezekkel a REST alapelvekkel, amik egyébként elég logikusak, nem lehet leírni klienseket.
Összefoglaló
Ha ezeket tekintjük REST alapelveknek:
- a http kérés minden része csak egy témakört ír le
- method : action, amit hívunk az erőforrásra
- path : az erőforrás azonosítója
- queryString : lekérdezés módosítók, pl: sort, limit, filter, stb...
- headers : megjelenítés módosítók, pl: accept: text/html, stb...
Akkor a jelenlegi technológiával csak adatszolgáltató hozható létre. Klienseket nem lehet létrehozni ezekkel az alapelvekkel, mert a kliensek url-jében szerepelnie kell a megjelenítés módosítóknak ahhoz, hogy a vissza gomb működjön. Szóval a kliensek továbbra is hagyományos webalkalmazások lesznek. Lehetőség van arra, hogy a klienst és az adatszolgáltatót egybeolvasszuk, mert az alapértelmezett megjelenítés módosítót nem muszáj betenni a kliens url-jébe:
users/inf3rno + templates/edit.tpl -> users/inf3rno/edit
vagy
users/inf3rno + templates/edit.tpl -> users/inf3rno/?form-type=PUT
Így a normál megjelenítés lesz mindig az alapértelmezett, az űrlap meg a másodlagos forma.
Lehetőség van arra is, hogy az űrlapokat nem tesszük lapozhatóvá:
users/inf3rno + templates/edit.tpl -> users/inf3rno
Így csökken a felhasználói élmény, de a kliens meg fog felelni a REST szabályoknak.
Lehetőség van arra is, hogy ezeket a REST szabályokat megváltoztassuk. Így a http kérés egy része több témakört leírhat, pl a queryString egyszerre leírhatja a lekérdezés módosítókat és a megjelenítés módosítókat. Ennek nyilván az az ára, hogy kevésbé lesz rendezett a REST service-ünk.
Én azt hiszem, hogy a klienseimet hagyományos webalkalmazásokként fogom leírni ezentúl, és teszek beléjük egy mapping-et, ami a webalkalmazás url-jét átalakítja az adatszolgáltató url-jére:
GET users/inf3rno -> GET users/inf3rno
GET users/inf3rno/edit -> PUT users/inf3rno
Azt hiszem ez a legátláthatóbb forma, mert az url ugyanolyan lesz, mint megszoktuk, viszont a REST szabályok sem sérülnek tőle, mert tisztáztuk a kódban, hogy a kliens nem REST service, hanem sima webalkalmazás. A kliens-ben egyébként is csak GET meg POST van, ami már elve nem felel meg a REST követelményeknek...
Okosabb kliens?
Szerintem semmi baj nincs azzal, ha a kliens először lekéri az adatokat, majd lekéri a sablon fájlt és a kettőből állítja elő a megjelenítésre szolgáló tartalmat. Ahogy az üres űrlapot is lekérheti, illetve az előzőleg kapott adatok meg megjelenítheti ebben. Szerintem közelebb vagyunk a szabványhoz, ha nem egy, hanem három erőforrásról beszélünk:
GET /template/profile
GET /form/profile
És, még csak azt az irányelvet sem kell felrúgni, hogy az üzenetek önleírók legyenek, mert az első üzenet tartalmazhatja a második kettő url-jét. Akár a fejlécbe, akár a tartalomban. (ahogy a HTML oldal is tartalmazza a CSS fájl elérhetőségét, amit a böngésző egy külön kérésként hoz le, megjelenítés során meg természetesen felhasználja azt)
Szerintem túlságosan ragaszkodsz a különböző url ajánlásokhoz, miközben (tudtommal) a REST ezekre egyáltalán nem tér ki. Ha megnézed a különböző kliens oldali framework-ökhöz készült REST leírásokat, akkor az első helyen szerepel, hogy miként írhatod át az url-eket a saját igényeid szerint. Hogy ne mindig az ExtJS-t említsem, Backbone-ban Backbone.Model.methodToURL
(félreértés ne legyen, nagyon hasznos, ha az url szép, illetve logikusan épül fel, de a szabvány erről nem szól)
Megjegyzés: Az Ajax hívásnál adhatsz meg PUT illetve DELETE típust, az működni fog.
Haha, nem ment át még mindig.
GET /template/profile
és
GET /form/profile
A history miatt sajnos minden ilyen aloldalhoz rendelni kell 1-1 url-t. Ennek az url-nek a tulajdonságai, amik érdekeltek, de ezeket már nem lehet úgy belőni, hogy az általáam elfogadott REST normáknak megfeleljenek.
Én egyébként nem sablonnal oldom meg, hanem különböző keretrendszerekkel és laconic-ot használó saját osztályokkal, szóval egyáltalán nem buta a kliensem. Egyszerűen, ha a kliensed webalkalmazás, akkor nem tud megfelelni a REST-es elvárásoknak az url struktúrája. Ha meg valamilyen asztali alkalmazás, akkor meg eleve nincs is benne url.
Szerintem egyébként túlvariálom, túl tökéletesre akarom megcsinálni :D
Btw egy aloldalhoz a kliens-ben, tehát egy kliens url-hez több adatforrás is tartozhat, szóval eleve rossz az a logika, hogy a REST service url-jeit próbálom megfeleltetni a kliens url-jeinek. Jobb inkább egy proxy-t csinálni a Service-ről, ami tárolja az erőforrásokat és method-okat, és annak átadni az adatot, illetve attól elkérni. Legközelebb majd megpróbálom ezt inkább itthon végiggondolni, mint hogy blogot írjak weblaborra :D
csak a kommunikáció!
Mi csak a szerver-kliens kommunikációról beszélünk, a kliens viselkedése szerintem nem része annak, hogy REST vagy nem REST...
Így van, de fél év kellett,
Kiszórtam külön subdomainekre
Szavadon foglak
:)
Jóvan, ha jönnek a
Nem vagyok én olyan...
Azt hogy szoktad kikerülni,
Elméletileg IE10 már támogatja rendesen ugyanazt, mint firefox és a többi. Most választhatok, hogy nem támogatom IE6, IE7, IE8, IE9 -et, vagy almappákba teszek mindent subdomain helyett. Egyelőre maradni fog subdomain-en, aztán majd később eldöntöm, hogy mi legyen. Belső rendszer lesz, szóval bizonyos fokig elő tudom írni, hogy mit használjanak hozzá.
JSONP
Ha nem Ajax hívásokat használsz, hanem az adatokat elposztolod és a szerver a megadott formában válaszol, akkor le tudod ezt szimulálni, de a hátulütők listája hosszabb, nincs PUT, DELETE, nincs speciális header. És ezért fontos az, hogy át tudd írni az url-eket, azaz a CRUD -hoz nem egy, hanem négy különböző url-t is megadhatsz.
ExtJS-ben a JSONP támogatást úgy oldották meg, hogy a proxy-ban csak egy property-t kell beállítani és a technikai megvalósítással nem kell törődnöm, a kód úgy néz ki, mintha Ajax hívás lenne, de közben persze nem az. (gyanítom ez máshol is ugyanúgy el van fedve)
Ahm, hát arra most sajnos
Még egy kérdés, láttam olyat
Saját megoldás már van, az valahogy így néz ki:
passz. :)
Okés, köszi, megpróbbálok
Na közben megtaláltam:
Találtam olyat is, hogy wadl vagy wsdl fájllal írták le a rest service-t, arra is van lehetőség.
Van egy rakat rest service leíró megoldás, de egyik sem tetszik igazán. Általában az átküldött adatok típusával is operálnak, nekem viszont elég lenne egyelőre csak a controller action-öket összekötni az url-ekkel.
Más is ötletelt az OPTIONS-re, úgy látom. Ezek szerint egyáltalán nem elvetélt ötlet options-re tenni a service leírását.
Szép lassan közelítek ahhoz,
http://www.bennadel.com/blog/2419-Mapping-RESTful-Resource-URIs-Onto-URL-Parameters-And-Server-Side-Events.htm
Végülis nekem csak a resource map-et kell leírnom ahhoz, hogy proxy-t tudjak generáltatni. Még a validáláshoz esetleg kellenek majd a típusok.
Valszeg valami ilyesmi lesz a végleges forma a resource mappingemnél:
Agyaltam ezen is, és arra jutottam, hogy ami gyűjteményre vonatkozik, azt jobb statikus metódosba tenni, ami meg konkrét bejegyzésre, azt meg sima metódusba. Jelenleg úgy áll a dolog, hogy ezt a lehetőséget nem fogom kihasználni, inkább maradok a sima controller objectnél.
Közben megírtam kliens oldalra, hogy a szervertől elkért json-ból generáljon proxy-t, illetve a kliens oldali url-ekhez a router-t szintén egy json fájlból generáltatom, aztán kattintásra, vagy közvetlen hívásra automatikusan betölti a megfelelő modult, és azon meghívja a megfelelő metódust. Nagy moduloknál lassú nettel és géppel talán lehet valamekkora késés így... Talán később teszek bele külön eseménykezelőt, hogy kirakja a loading feliratot, vagy ilyesmi. A service proxy-ját ennél jóval bonyolultabb automatizálni, mert ott el kell dönteni, hogy a bejövő jsont milyen Model, Collection, Record, stb... osztályba szórom bele. Lehet, hogy jobban járok, ha a proxy nyers adatot küld és fogad, de úgy meg nem használom ki a Backbone.sync nyújtotta kényelmet...
Ez esetleg segíthet
Köszi, majd ránézek, de