Best practice per API REST: Come accettare l'elenco dei valori dei parametri come input [chiuso]


410

Stiamo lanciando una nuova API REST e volevo alcuni input della community sulle migliori pratiche su come dovremmo avere formattato i parametri di input:

Al momento, la nostra API è molto incentrata su JSON (restituisce solo JSON). Il dibattito sulla necessità o meno di restituire XML è un problema separato.

Poiché il nostro output API è incentrato su JSON, abbiamo seguito un percorso in cui i nostri input sono un po 'incentrati su JSON e ho pensato che potrebbe essere conveniente per alcuni ma strano in generale.

Ad esempio, per ottenere alcuni dettagli del prodotto in cui è possibile estrarre più prodotti contemporaneamente, al momento disponiamo di:

http://our.api.com/Product?id=["101404","7267261"]

Dovremmo semplificare questo come:

http://our.api.com/Product?id=101404,7267261

O avere un input JSON a portata di mano? Più di un dolore?

Potremmo voler accettare entrambi gli stili, ma questa flessibilità provoca effettivamente maggiore confusione e mal di testa (manutenibilità, documentazione, ecc.)?

Un caso più complesso è quando vogliamo offrire input più complessi. Ad esempio, se vogliamo consentire più filtri nella ricerca:

http://our.api.com/Search?term=pumas&filters={"productType":["Clothing","Bags"],"color":["Black","Red"]}

Non vogliamo necessariamente mettere i tipi di filtro (es. ProductType e color) come nomi di richieste come questo:

http://our.api.com/Search?term=pumas&productType=["Clothing","Bags"]&color=["Black","Red"]

Perché volevamo raggruppare tutti gli input di filtro insieme.

Alla fine, importa davvero? Potrebbe essere probabile che ci siano così tanti programmi di utilità JSON là fuori che il tipo di input non ha molta importanza.

So che i nostri client JavaScript che effettuano chiamate AJAX all'API possono apprezzare gli input JSON per semplificarne la vita.

Risposte:


341

Un passo indietro

Innanzitutto, REST descrive un URI come un ID universalmente univoco. Troppe persone rimangono intrappolate nella struttura degli URI e quali URI sono più "riposanti" di altri. Questo argomento è tanto ridicolo quanto dire nominare qualcuno "Bob" è meglio che nominarlo "Joe" - entrambi i nomi ottengono il compito di "identificare una persona". Un URI non è altro che un nome univoco universale .

Quindi agli occhi di REST discutere se ?id=["101404","7267261"]è più riposante ?id=101404,7267261o in \Product\101404,7267261qualche modo inutile.

Ora, detto questo, molte volte il modo in cui sono costruiti gli URI può di solito servire come un buon indicatore per altri problemi in un servizio RESTful. Ci sono un paio di bandiere rosse nei tuoi URI e domande in generale.

suggerimenti

  1. URI multipli per la stessa risorsa e Content-Location

    Potremmo voler accettare entrambi gli stili, ma questa flessibilità provoca effettivamente maggiore confusione e mal di testa (manutenibilità, documentazione, ecc.)?

    Gli URI identificano le risorse. Ogni risorsa dovrebbe avere un URI canonico. Questo non significa che non puoi avere due URI che puntano alla stessa risorsa ma ci sono modi ben definiti per farlo. Se decidi di utilizzare sia il formato JSON che quello basato su elenco (o qualsiasi altro formato) devi decidere quale di questi formati è l' URI canonico principale . Tutte le risposte ad altri URI che puntano alla stessa "risorsa" dovrebbero includere l' Content-Locationintestazione .

    Attenersi all'analogia dei nomi, avere URI multipli è come avere soprannomi per le persone. È perfettamente accettabile e spesso a volte abbastanza utile, tuttavia se sto usando un soprannome probabilmente voglio ancora conoscere il loro nome completo - il modo "ufficiale" di riferirsi a quella persona. In questo modo quando qualcuno menziona qualcuno con il suo nome completo, "Nichloas Telsa", so che stanno parlando della stessa persona a cui mi riferisco come "Nick".

  2. "Cerca" nel tuo URI

    Un caso più complesso è quando vogliamo offrire input più complessi. Ad esempio, se vogliamo consentire più filtri nella ricerca ...

    Una mia regola empirica generale è che, se l'URI contiene un verbo, potrebbe essere un'indicazione che qualcosa è spento. Gli URI identificano una risorsa, tuttavia non dovrebbero indicare cosa stiamo facendo a quella risorsa. Questo è il lavoro di HTTP o in termini riposanti, la nostra "interfaccia uniforme".

    Per sconfiggere il nome dell'analogia, usare un verbo in un URI è come cambiare il nome di qualcuno quando si desidera interagire con loro. Se interagisco con Bob, il nome di Bob non diventa "BobHi" quando voglio salutarlo. Allo stesso modo, quando vogliamo "cercare" i prodotti, la nostra struttura URI non dovrebbe cambiare da "/ Prodotto / ..." a "/ Cerca / ...".

Rispondere alla tua domanda iniziale

  1. Per quanto riguarda ["101404","7267261"]vs 101404,7267261: il mio suggerimento qui è di evitare la sintassi JSON per motivi di semplicità (vale a dire che non è necessario che gli utenti eseguano la codifica URL quando non è necessario). Renderà la tua API un po 'più utilizzabile. Meglio ancora, come altri hanno raccomandato, andare con il application/x-www-form-urlencodedformato standard in quanto probabilmente sarà più familiare agli utenti finali (ad esempio ?id[]=101404&id[]=7267261). Potrebbe non essere "carino", ma URI graziosi non significa necessariamente URI utilizzabili. Tuttavia, per ribadire il mio punto iniziale, alla fine, quando si parla di REST, non importa. Non soffermarti troppo su di esso.

  2. Il tuo esempio di URI di ricerca complessa può essere risolto in modo molto simile all'esempio del tuo prodotto. Consiglierei di tornare al application/x-www-form-urlencodedformato in quanto è già uno standard che molti conoscono. Inoltre, consiglierei di unire i due.

Il tuo URI ...

/Search?term=pumas&filters={"productType":["Clothing","Bags"],"color":["Black","Red"]}    

Il tuo URI dopo essere stato codificato URI ...

/Search?term=pumas&filters=%7B%22productType%22%3A%5B%22Clothing%22%2C%22Bags%22%5D%2C%22color%22%3A%5B%22Black%22%2C%22Red%22%5D%7D

Può essere trasformato in ...

/Product?term=pumas&productType[]=Clothing&productType[]=Bags&color[]=Black&color[]=Red

Oltre a evitare il requisito della codifica URL e a rendere le cose un po 'più standard, ora omogeneizza un po' l'API. L'utente sa che se desidera recuperare un prodotto o un elenco di prodotti (entrambi sono considerati una singola "risorsa" in termini RESTful), sono interessati agli /Product/...URI.


67
Volevo dare seguito e notare che la []sintassi non è sempre supportata (e nonostante sia comune, potrebbe persino violare le specifiche URI). Alcuni server HTTP e linguaggi di programmazione preferiranno semplicemente ripetere il nome (ad es productType=value1&productType=value2.).
nategood,

1
La domanda iniziale con questa query .. "/ Cerca? Term = pumas & filters = {" productType ": [" Abbigliamento "," Borse "]," color ": [" Nero "," Rosso "]}" si traduce in .. . (productType == abbigliamento || productType == borse) && (color == black || color == red) MA LA TUA SOLUZIONE: / Product? term = pumas & productType [] = Clothing & productType [] = Bags & color [] = Black & color [] = Il rosso sembra tradursi in ... O (productType == abbigliamento || productType == borse || color == nero || color == rosso) o O (productType == abbigliamento && productType == borse && color == black && color == red) Il che mi sembra leggermente diverso. O ho capito male?
Thomas Cheng,

2
Che dire degli input nella richiesta post? Volevo sapere se stiamo aggiornando una risorsa, quindi è una cattiva pratica inviare la query / filtro e i dati nel corpo in un formato standard. per es. se voglio modificare i dati relativi all'utente utilizzando l'API /user/e nel corpo, manderò { q:{}, d: {} }con qcome query con l'utente sarà interrogato nel DB e dcome i dati modificati.
molecola

1
Cosa fai quando l'elenco può essere molto grande? L'URI ha una lunghezza limitata a seconda del browser. In genere sono passato a una richiesta di posta e ho inviato l'elenco nel corpo. Qualche suggerimento lì?
Troia Cosentino,

4
Dovrebbe essere MOLTO grande (consultare stackoverflow.com/questions/417142/… ), ma sì, nei casi più estremi, potrebbe essere necessario utilizzare il corpo della richiesta. POSTare domande per il recupero dei dati è una di quelle cose che RESTafarians ama discutere.
nategood,

234

Il modo standard per passare un elenco di valori come parametri URL è ripeterli:

http://our.api.com/Product?id=101404&id=7267261

La maggior parte del codice server interpreterà questo come un elenco di valori, anche se molti hanno semplificazioni a valore singolo, quindi potresti dover cercare.

Anche i valori delimitati vanno bene.

Se hai bisogno di inviare JSON al server, non mi piace vederlo nell'URL (che è un formato diverso). In particolare, gli URL hanno un limite di dimensioni (in pratica se non in teoria).

Il modo in cui ho visto alcuni eseguire una query complicata RESTfully è in due passaggi:

  1. POST i requisiti della query, ricevendo un ID (essenzialmente creando una risorsa per i criteri di ricerca)
  2. GET la ricerca, facendo riferimento all'ID sopra
  3. facoltativamente ELIMINA i requisiti della query, se necessario, ma nota che sono disponibili per il riutilizzo.

8
Grazie Kathy. Penso di essere con te e non mi piace molto vedere anche JSON nell'URL. Tuttavia, non sono un fan di fare un post per una ricerca che è un'operazione intrinseca GET. Puoi indicare un esempio di questo?
whatupwilly,

1
Se le query possono funzionare come parametri semplici, farlo. La fonte proveniva dalla mailing list di rest
Kathy Van Stone

2
Se vuoi solo mostrare due risorse, la risposta di James Westgate è più tipica
Kathy Van Stone,

Questa è la risposta corretta Nel prossimo futuro sono sicuro che vedremo alcuni filter = id in (a, b, c, ...) supportati da OData o qualcosa del genere.
Bart Calixto,

Questo è il modo in cui funziona Akka HTTP
Joan,

20

Primo:

Penso che tu possa farlo in 2 modi

http://our.api.com/Product/<id> : se vuoi solo un disco

http://our.api.com/Product : se vuoi tutti i record

http://our.api.com/Product/<id1>,<id2> : come James ha suggerito può essere un'opzione poiché ciò che segue il tag Product è un parametro

O quello che mi piace di più è:

È possibile utilizzare Hypermedia come motore di proprietà dello stato dell'applicazione (HATEOAS) di un RestFul WS ed effettuare una chiamata http://our.api.com/Productche dovrebbe restituire gli URL equivalenti di http://our.api.com/Product/<id>e chiamarli dopo questo.

Secondo

Quando devi fare domande sulle chiamate url. Suggerirei di utilizzare nuovamente HATEOAS.

1) Effettua una chiamata a http://our.api.com/term/pumas/productType/clothing/color/black

2) Chiama http://our.api.com/term/pumas/productType/clothing,bags/color/black,red

3) (Usando HATEOAS) Chiama il ` http://our.api.com/term/pumas/productType/ -> ricevi gli url tutti gli URL possibili per l'abbigliamento -> chiama quelli che vuoi (abbigliamento e borse) - > ricevi i possibili URL di colore -> chiama quelli che desideri


1
Sono stato messo in una situazione simile qualche giorno fa, dovendo mettere a punto un (HATEOAS) api di riposo per ottenere un elenco filtrato (ampio) di oggetti e ho appena scelto la tua seconda soluzione. Ricordare l'api ancora e ancora per ognuno è un po 'eccessivo?
Sansone

Dipende molto dal tuo sistema .... Se è semplice con poche "opzioni" probabilmente dovrebbe essere eccessivo. Tuttavia, se hai elenchi molto grandi, può diventare davvero problematico fare tutto in una sola grande chiamata, inoltre se la tua API è pubblica può diventare complicata per gli utenti (se è privata dovrebbe essere più facile ... basta insegnare agli utenti che conosci). In alternativa, è possibile implementare entrambi gli stili, l'HATEOAS e una chiamata "array non riposante" per utenti esperti
Diego Dias,

Sto costruendo un servizio web API riposante in rotaie e devo seguire la stessa struttura dell'URL di cui sopra ( our.api.com/term/pumas/productType/clothing/color/black ). Ma non sono sicuro di come configurare i percorsi di conseguenza.
rubyist,

termine, productType e colore sono i tuoi controller? Se è così, devi solo fare: risorse: termine fare risorse: productType fare risorse: fine colore
Diego Dias

productType e color sono i parametri. Quindi il parametro di productType è l'abbigliamento e il parametro di abbigliamento è nero
rubyist il

12

Potresti voler dare un'occhiata a RFC 6570 . Questa specifica del modello URI mostra molti esempi di come gli URL possono contenere parametri.


1
La sezione 3.2.8 sembra essere ciò che è applicabile. Anche se vale la pena notare che questo è solo uno standard proposto e non sembra essersi spostato oltre quel punto.
Mike Post,

3
@MikePost Ora che IETF è passato a un processo di maturità in due fasi per i documenti "standard track", mi aspetto che 6570 rimanga così per qualche altro anno prima di passare a uno "Internet Standard". tools.ietf.org/html/rfc6410 La specifica è estremamente stabile, ha molte implementazioni ed è ampiamente usata.
Darrel Miller,

Ah, non ero a conoscenza di quel cambiamento. (Oppure, TIL IETF ora è più ragionevole.) Grazie!
Mike Post,

8

Primo caso:

Una normale ricerca del prodotto sarebbe simile a questa

http://our.api.com/product/1

Quindi sto pensando che la migliore pratica sarebbe per te fare questo

http://our.api.com/Product/101404,7267261

Secondo caso

Cerca con i parametri di querystring: va bene così. Sarei tentato di combinare i termini con AND e OR invece di utilizzare [].

PS Questo può essere soggettivo, quindi fai quello che ti senti a tuo agio.

Il motivo per inserire i dati nell'URL è che il link può essere incollato su un sito / condiviso tra gli utenti. Se questo non è un problema, utilizzare invece un JSON / POST.

EDIT: Sulla riflessione penso che questo approccio sia adatto a un'entità con una chiave composta, ma non a una query per più entità.


3
Ovviamente, in entrambi gli esempi, il trailing /non dovrebbe essere presente poiché l'URI indirizza una risorsa, non una raccolta.
Lawrence Dol

2
Ho sempre pensato che i verbi HTTP, in un uso REST, dovevano fare azioni specifiche, e questa era la linea guida: GET: recupera / leggi oggetto, POST crea oggetto, PUT aggiorna oggetto esistente ed ELIMINA elimina un oggetto. Quindi non userei un POST per recuperare qualcosa. Se voglio un elenco di oggetti in particolare (filtro), farei un GET con un elenco di parametri url (separati da una virgola sembra buono)
Alex

1

Mi schiererò dalla risposta di nategood in quanto è completa e sembra aver soddisfatto le tue esigenze. Tuttavia, vorrei aggiungere un commento sull'identificazione di più (1 o più) risorse in questo modo:

http://our.api.com/Product/101404,7267261

Nel fare ciò, tu:

Complessa i clienti costringendoli a interpretare la tua risposta come un array, che per me è contro intuitivo se faccio la seguente richiesta:http://our.api.com/Product/101404

Crea API ridondanti con un'API per ottenere tutti i prodotti e quella sopra per ottenere 1 o più. Dal momento che non dovresti mostrare più di 1 pagina di dettagli a un utente per il bene di UX, credo che avere più di 1 ID sarebbe inutile e puramente usato per filtrare i prodotti.

Potrebbe non essere così problematico, ma dovrai gestire questo lato server restituendo una singola entità (verificando se la tua risposta ne contiene una o più) o consenti ai clienti di gestirla.

Esempio

Voglio ordinare un libro da Amazing . So esattamente di che libro si tratta e lo vedo nella lista quando cerco libri Horror:

  1. 10.000 linee sorprendenti, 0 test incredibili
  2. Il ritorno del fantastico mostro
  3. Dupliciamo un codice straordinario
  4. L'incredibile inizio della fine

Dopo aver selezionato il secondo libro, vengo reindirizzato a una pagina che illustra in dettaglio la parte del libro di un elenco:

--------------------------------------------
Book #1
--------------------------------------------
    Title: The return of the amazing monster
    Summary:
    Pages:
    Publisher:
--------------------------------------------

O in una pagina che mi fornisce solo i dettagli completi di quel libro?

---------------------------------
The return of the amazing monster
---------------------------------
Summary:
Pages:
Publisher:
---------------------------------

La mia opinione

Suggerirei di utilizzare l'ID nella variabile path quando l'unicity è garantita quando si ottengono i dettagli di questa risorsa. Ad esempio, le API di seguito suggeriscono diversi modi per ottenere i dettagli per una risorsa specifica (supponendo che un prodotto abbia un ID univoco e che una specifica per quel prodotto abbia un nome univoco e che si possa navigare dall'alto verso il basso):

/products/{id}
/products/{id}/specs/{name}

Nel momento in cui hai bisogno di più di una risorsa, suggerirei di filtrare da una raccolta più ampia. Per lo stesso esempio:

/products?ids=

Naturalmente, questa è la mia opinione in quanto non è imposta.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.