Check alle échte Black Friday-deals Ook zo moe van nepaanbiedingen? Wij laten alleen échte deals zien

RESTful API design, hoe verwijzen naar andere resources?

Pagina: 1
Acties:

  • Alex)
  • Registratie: Juni 2003
  • Laatst online: 18-11 20:57
*Dit topic is afgesplitst van De Devschuur Coffee Corner - Iteratie 6

Zo jammer hè, dan kom je een guide tegen die beweert de wijsheid in pacht te hebben over hoe je RESTful services ontwerpt, en dan zit er al in één van de eerste samples een fout.
To create a new product, issue a request as in the following example:

PUT /products
<product>...</product>
Nee lul, dat moet POST zijn volgens de specs. PUT is om een bestaand object te vervangen.

En wat ik eigenlijk zocht heb ik nog steeds niet gevonden: wat guidance over hoe je het beste naar een ander item kunt linken, bijvoorbeeld: voeg product "suiker" met ID 123 toe aan mijn /shoppingcart.

:(

[ Voor 9% gewijzigd door RobIII op 21-07-2014 23:24 ]

We are shaping the future


  • sky-
  • Registratie: November 2005
  • Niet online

sky-

qn nna 👌

Waarom maak je dan geen rest call met '/addproducttocart' en post je het id van het product mee? Met uiteraard een qty. Wat je precies bedoelt met 'naar een ander product linken' is een raadsel.

don't be afraid of machines, be afraid of the people who build and train them.


  • Blubber
  • Registratie: Mei 2000
  • Niet online
sky- schreef op maandag 21 juli 2014 @ 19:33:
Waarom maak je dan geen rest call met '/addproducttocart' en post je het id van het product mee? Met uiteraard een qty. Wat je precies bedoelt met 'naar een ander product linken' is een raadsel.
Dat is niet volgens de 'spec', dan zou het een POST naar /cart/<cart_id>/products/ zijn, maar ik vermoed dat hij dat niet bedoeld.

  • sky-
  • Registratie: November 2005
  • Niet online

sky-

qn nna 👌

Zolang je het documenteert is dát de spec (want die is er namelijk niet..). ;)

Volgens 'spec' zou je meerdere dingen kunnen doen:

/product/<id>/addtocart
/cart/<id>/addproduct

En Oracle ATG doet het zelfs zo:
code:
1
2
3
4
curl -L -v -b customer_cookies.txt -H "Content-Type: application/json" -d "{
"catalogRefIds" : "xsku2085", "productId" : "xprod2085", "giftlistId" : "gl40007",
"giftlistItemId" : "gi40001", "quantity": 1}" "http://localhost:8280/rest/model/
atg/commerce/order/purchase/CartModifierActor/addItemToOrder"

[ Voor 3% gewijzigd door sky- op 21-07-2014 19:48 ]

don't be afraid of machines, be afraid of the people who build and train them.


  • Douweegbertje
  • Registratie: Mei 2008
  • Laatst online: 30-10 12:53

Douweegbertje

Wat kinderachtig.. godverdomme

Blubber schreef op maandag 21 juli 2014 @ 19:38:
[...]


Dat is niet volgens de 'spec', dan zou het een POST naar /cart/<cart_id>/products/ zijn, maar ik vermoed dat hij dat niet bedoeld.
Volgens mij wil je uiteindelijk gewoon iets hebben van POST cart/artikelen&cart=xxxx&artikel=xxxx

Overigens: Unlike SOAP-based web services, there is no "official" standard for RESTful web APIs

  • Alex)
  • Registratie: Juni 2003
  • Laatst online: 18-11 20:57
sky- schreef op maandag 21 juli 2014 @ 19:33:
Waarom maak je dan geen rest call met '/addproducttocart' en post je het id van het product mee? Met uiteraard een qty. Wat je precies bedoelt met 'naar een ander product linken' is een raadsel.
sky- schreef op maandag 21 juli 2014 @ 19:43:
Zolang je het documenteert is dát de spec (want die is er namelijk niet..). ;)

Volgens 'spec' zou je meerdere dingen kunnen doen:

/product/<id>/addtocart
/cart/<id>/addproduct

En Oracle ATG doet het zelfs zo:
code:
1
2
3
4
curl -L -v -b customer_cookies.txt -H "Content-Type: application/json" -d "{
"catalogRefIds" : "xsku2085", "productId" : "xprod2085", "giftlistId" : "gl40007",
"giftlistItemId" : "gi40001", "quantity": 1}" "http://localhost:8280/rest/model/
atg/commerce/order/purchase/CartModifierActor/addItemToOrder"
Douweegbertje schreef op maandag 21 juli 2014 @ 19:48:
[...]


Volgens mij wil je uiteindelijk gewoon iets hebben van POST cart/artikelen&cart=xxxx&artikel=xxxx

Overigens: Unlike SOAP-based web services, there is no "official" standard for RESTful web APIs
Stel, ik heb het volgende endpoint in mijn systeem:

<root>/ordersystem/orders/12345

Dit endpoint geeft data volgens een volgende structuur terug (wat we ook intern gebruiken):
C#:
1
2
3
4
5
6
7
public class Order
{
    public int              OrderId     { get; set; }
    public DateTime         CreatedOn   { get; set; }
    public List<LineItem>   LineItems   { get; set; }
    public OrderStatus      Status      { get; set; }
}


Als ik een nieuwe order wil maken moet ik eerst een root-object maken (een soort-van shopping cart) om er daarna LineItems in te zetten. Een LineItem heeft bij mij intern de volgende structuur:

C#:
1
2
3
4
5
6
7
8
9
10
public class LineItem
{
    public int                              LineItemId  { get; set; }
    public Order                            Order       { get; set; }
    public Product                          Product     { get; set; }
    public User                             OrderedFor  { get; set; }
    public Address                          Destination { get; set; }
    public DateTime                         OrderDate   { get; set; }
    public Dictionary<string, List<string>> Fields      { get; set; }
}


Ik kan dit ook redelijk makkelijk gebruiken als ik een compleet gevuld Order-object wil ophalen (gewoon het object inclusief alle childs ophalen en door een JSON-formatter rammen).

Als ik een nieuw LineItem wil toevoegen aan een bestaande Order, roep ik de volgende URL aan:
POST <root>/ordersystem/orders/12345/lineitems

Maar ik zit te dubben over de payload ervan. Ik vind het een beetje contra-intuïtief dat de client een Order-klasse moet instantiëren, een Product-klasse moet instantiëren, een User-klasse moet instantiëren voor de OrderedFor, enzovoorts.

De objecten die je ziet zijn intern keys naar rijen in verschillende tabellen, maar ik twijfel dus over hoe ik dit moet exposen naar de buitenwereld. Het meest eenvoudige zou de volgende payload zijn:
POST <root>/ordersystem/orders/12345/lineitems
Product=37&OrderedFor=82&Destination=79&OrderDate=2014-07-21T19:43:19Z


Dit is echter niet mogelijk vanwege het "Fields"-veld dat ook ingevuld moet worden (wat key-valuepairs bevat die specifiek zijn voor het gekozen product, zoals bijvoorbeeld een maat of kleur). In werkelijkheid wordt er dus een complex JSON-object gegenereerd en over de lijn gestuurd. Dat ziet er ongeveer als volgt uit:
JavaScript:
1
2
3
4
5
6
7
8
9
{
    "Product":      { "ProductId":  37 },
    "OrderedFor":   { "UserId": 82 },
    "Destination":  { "AddressId": 79 },
    "OrderDate":    "2014-07-21T19:43:19Z",
    "Fields":       {
                        "Aap":  ["noot", "mies"]
                    }
}


Wanneer de client een Order ophaalt zijn alle LineItems gevuld, inclusief informatie over de producten en adressen. Ik kan makkelijk een tabel tonen waarbij ik databind op LineItem.Product.Description.

Ik heb mijn twijfels over hoe ik verwacht dat clients naar de server gaan posten. In het bovenstaande voorbeeld zijn er 3 properties waarin maar 1 veld wordt gevuld (het ID). Serverside hebben de bijbehorende tabellen natuurlijk veel meer informatie dan slechts een nummertje, maar dat is voor de client in deze context niet van belang.

Als ik ervoor kies om de LineItem-klasse plat te slaan voor POST (en PUT), betekent dit dat ik twee versies moet gaan bijhouden van mijn ViewModel. Eentje voor GET, eentje voor POST. Dit voelt een beetje 'vies' aan. Aan de andere kant, als ik één ViewModel aanhoud betekent dit dat de client altijd een genest object moet gaan instantiëren terwijl voor de client alleen maar de IDs van belang zijn.

Ik zit te twijfelen over hoe ik dit het beste kan oplossen, technisch gezien.

We are shaping the future


  • Douweegbertje
  • Registratie: Mei 2008
  • Laatst online: 30-10 12:53

Douweegbertje

Wat kinderachtig.. godverdomme

Ik wil wel reageren, maar dan worden de modjes b00s :p Maak eens een topic? (of vraag nu voor een afsplitsing?)

  • Alex)
  • Registratie: Juni 2003
  • Laatst online: 18-11 20:57
Douweegbertje schreef op maandag 21 juli 2014 @ 22:13:
Ik wil wel reageren, maar dan worden de modjes b00s :p Maak eens een topic? (of vraag nu voor een afsplitsing?)
Bij nader inzien maar om een afsplitsing gevraagd, het is een iets te grote post geworden :Y

We are shaping the future


  • pderaaij
  • Registratie: Oktober 2005
  • Laatst online: 18-08 20:16
Sowieso zou een API niet vast moeten zitten aan het HTTP protocol. Probeer je library zo te maken dat hij ook over SMTP zou kunnen werken als je in een experimentele bui bent.

Daarnaast kun je eens kijken naar de HAL specificatie -> http://stateless.co/hal_specification.html. Geeft je alle informatie om een navigatable en explorable API te maken.

  • HMS
  • Registratie: Januari 2004
  • Laatst online: 17-11 00:33

HMS

Jouw probleem lijkt op het probleem wat CQRS wil oplossen, het verschil tussen het lezen van data en het schrijven van data. Dit zijn 2 aparte dingen die ieder hun eigenaardigheden hebben, en die jij in 1 model probeert te vangen.

  • kwaakvaak_v2
  • Registratie: Juni 2009
  • Laatst online: 10-10 08:02
Douweegbertje schreef op maandag 21 juli 2014 @ 19:48:
[...]


Volgens mij wil je uiteindelijk gewoon iets hebben van POST cart/artikelen&cart=xxxx&artikel=xxxx
Dat wil je bij een goede REST API dus juist niet ;) Het idee is dat je juist parameters in je URL verwerkt, en de andere data in je POST object stopt.

zie http://www.restapitutorial.com/lessons/restquicktips.html (Niet dat dit de heilige graal is, maar het snijd wel soort van hout wat er staat)

Driving a cadillac in a fool's parade.


  • Jurgle
  • Registratie: Februari 2003
  • Laatst online: 20-11 15:05

Jurgle

100% Compatible

N:M relaties modelleer ik in een REST schema bijna altijd ongeveer zoals sky- aangeeft:
sky- schreef op maandag 21 juli 2014 @ 19:43:
Volgens 'spec' zou je meerdere dingen kunnen doen:

/product/<id>/addtocart
/cart/<id>/addproduct
Maar dan geen werkwoorden in de REST-URLs, die zitten immers in je HTTP-REQUEST.

Product toevoegen aan winkelwagen:
POST /cart/<cart_id>/product/<product_id>/

Product verwijderen uit de winkelwagen
DELETE /cart/<cart_id>/product/<product_id>/

<cart_id> is nodig in geval je, 'volgens de specs', stateless wil zijn. Het blijft trouwens imho nogsteeds 'volgens de specs' als je contextgebonden calls doet. In dit geval afhankelijk van de (evt ingelogde) sessie van de gebruiker, waarbij je <cart_id>/ zou kunnen weglaten.

Eventueel zou je een body kunnen meegeven waarbij je de quantity aangeeft, al is dat bij een DELETE dan weer niet 'volgens de specs'.

GET /cart/<cart_id>/product/<product_id>
zou dan de quantity aangeven van dat product in de de cart.

GET /cart/<cart_id>/product/
zou dan een lijst teruggeven van de producten in de cart.

Je kunt je 'product-entiteiten' in deze uitbreiden met een quantity. Je 'bekijkt' ze immers in de context van de cart, niet 'zonder' context als je zou doen bij
GET /product/<product_id>

Volgens mij is RESTfulness meer een scalar dan een boolean. Hiermee bedoel ik dat je interface niet WEL/NIET RESTful is, maar zo veel mogelijk RESTful is, immers:
Douweegbertje schreef op maandag 21 juli 2014 @ 19:48:
[...]
Overigens: Unlike SOAP-based web services, there is no "official" standard for RESTful web APIs
Vergeet trouwens niet om, om dichter bij die 100% RESTful te komen, in je response-payloads van GET's op niet de entiteiten zelf altijd de URL naar de betreffende entiteit mee te geven. Een
GET /cart/<cart_id>/product/<product_id>
response geeft naast evt de quantity en alle andere info over het product die je al API-ontwikkelaar redelijkerwijs verwacht handig te zijn voor de API-consumer (zoals de productnaam, om voor lijstjes niet n+1 requests te krijgen) ook ALTIJD de URL terug van het product zelf:
GET /product/<product_id>

Is dit wat je min of meer bedoeld?

[ Voor 0% gewijzigd door Jurgle op 22-07-2014 11:55 . Reden: Frikkin dt fout!!!1 (en ergens haakjes vergeten) ]

My opinions may have changed but not the fact that I am right ― Ashleigh Brilliant


  • Alex)
  • Registratie: Juni 2003
  • Laatst online: 18-11 20:57
Iedereen bedankt voor het meedenken, jullie input is zeer waardevol! Ik vind CQRS zeker een goed punt om rekening mee te houden, ik ga het er morgen met mijn collega's over hebben om uit te komen op een goede API. :)

Dank!

We are shaping the future


  • Kajel
  • Registratie: Oktober 2004
  • Laatst online: 09-11 18:41

Kajel

Development in Style

Jurgle is spot on wat mij betreft! Zeker ook met betrekking tot het niet gebruiken van verbs in je URI's.
Zoals andere zeggen is REST geen protocol. Wat het wel is, is een set guidelines die bedacht zijn om APIs zo makkelijk mogelijk in het gebruik te maken, en om APIs zoveel mogelijk gebruik te laten maken van alle tools die het HTTP protocol biedt. Je houden aan REST principes doet meer goed dan kwaad voor de gebruikers van je API. Ik ben het dus zeker niet eens met iedereen hier die het maar afdoet als "het is geen protocol, dus houd je er vooral niet aan", om vervolgens met dit soort afschrikwekkende ideeën te komen:
/product/<id>/addtocart
/cart/<id>/addproduct
Of dit:
Volgens mij wil je uiteindelijk gewoon iets hebben van POST cart/artikelen&cart=xxxx&artikel=xxxx
Of dit juweeltje:
Sowieso zou een API niet vast moeten zitten aan het HTTP protocol. Probeer je library zo te maken dat hij ook over SMTP zou kunnen werken als je in een experimentele bui bent.
Doe dat laatste in ieder zeker wel (vast zitten aan HTTP)! Geen enkele API gebruiker zal een boodschap hebben aan de optie om over SMTP (REALLY?!) de API aan te roepen. Ze willen gewoon een duidelijke interface die onveranderlijk blijft. HTTP biedt die interface, en is bekend bij alle developers.

Dit gratis e-Book is een enorme aanrader als je meer wil weten over REST API design: http://info.apigee.com/Portals/62317/docs/web%20api.pdf

Ik ben het niet met alles 100% eens (het is en blijft geen wetmatigheid, dat REST), maar zeker 80% is zeer bruikbaar en toepasbaar!

[ Voor 3% gewijzigd door Kajel op 23-07-2014 14:40 ]

Pagina: 1