Impaginazione in un'applicazione web REST


329

Questa è una riformulazione più generica di questa domanda (con l'eliminazione delle parti specifiche di Rails)

Non sono sicuro di come implementare l'impaginazione su una risorsa in un'applicazione Web RESTful. Supponendo che io abbia una risorsa chiamata products, quale dei seguenti ritieni sia l'approccio migliore e perché:

1. Utilizzando solo stringhe di query

per esempio. http://application/products?page=2&sort_by=date&sort_how=asc
Il problema qui è che non riesco a utilizzare la memorizzazione nella cache a pagina intera e anche l'URL non è molto pulito e facile da ricordare.

2. Utilizzo delle pagine come risorse e stringhe di query per l'ordinamento

per esempio. http://application/products/page/2?sort_by=date&sort_how=asc
In questo caso, il problema che si vede è che http://application/products/pages/1non è una risorsa unica poiché l'utilizzo sort_by=pricepuò produrre un risultato totalmente diverso e non riesco ancora a utilizzare la memorizzazione nella cache della pagina.

3. Utilizzo delle pagine come risorse e di un segmento URL per l'ordinamento

per esempio. http://application/products/by-date/page/2
Personalmente non vedo alcun problema nell'uso di questo metodo, ma qualcuno mi ha avvertito che questo non è un buon modo di procedere (non ha fornito un motivo, quindi se sai perché non è raccomandato, faccelo sapere)

Eventuali suggerimenti, opinioni, critiche sono più che benvenuti. Grazie.


34
Questa è un'ottima domanda
Iain Holder,

7
Domanda bonus: come fanno le persone a specificare le dimensioni della pagina?
Heiko Rupp,

Non dimenticare i parametri Matrix w3.org/DesignIssues/MatrixURIs.html
CMCDragonkai

Risposte:


66

Penso che il problema con la versione 3 sia più un problema di "punto di vista" - vedi la pagina come la risorsa o i prodotti sulla pagina.

Se vedi la pagina come risorsa, è una soluzione perfettamente valida, poiché la query per pagina 2 genererà sempre pagina 2.

Ma se vedi i prodotti nella pagina come la risorsa hai il problema che i prodotti nella pagina 2 potrebbero cambiare (vecchi prodotti eliminati o altro), in questo caso l'URI non restituisce sempre le stesse risorse.

Ad esempio, un cliente memorizza un collegamento alla pagina dell'elenco dei prodotti X, alla successiva apertura del collegamento il prodotto in questione potrebbe non essere più nella pagina X.


6
Bene, ma se elimini qualcosa non dovrebbe esserci qualcos'altro nello stesso URI. Se si eliminano tutti i prodotti della pagina X - la pagina X potrebbe essere ancora valida ma contiene ora i prodotti della pagina X + 1. Quindi l'URI per la pagina X è diventato l'URI per la pagina X + 1 se lo si vede nella "vista delle risorse del prodotto ".
Fionn,

1
> Se vedi la pagina come risorsa, è una soluzione perfettamente valida, poiché la query per pagina 2 genererà sempre pagina 2. Ha anche senso? Lo stesso URL (qualsiasi URL che menziona la pagina 2) restituirà sempre la pagina 2, indipendentemente da quale risorsa.
temoto,

2
Vedere la pagina come risorsa probabilmente dovrebbe introdurre POST / foo / page per creare una nuova pagina, giusto?
temoto,

18
La tua risposta va senza problemi a "la soluzione corretta è 1", ma non la indica.
temoto,

2
Nella mia mente, la pagina è un concetto fluttuante e non correlato al dominio sottostante. E quindi non dovrebbe essere considerato come una risorsa. Intendo fluttuare nel senso che è fluido, che il concetto di pagina cambia con il contesto; un utente della tua API può essere un'app mobile, che può consumare solo 2 prodotti per pagina, mentre l'altro è un'app macchina che può consumare l'intero dannato elenco. In breve, la pagina è una "rappresentazione" dell'entità di dominio (prodotto) sottostante e non deve essere inclusa come parte dell'URL; solo come parametro di query.
Kingz,

106

Sono d'accordo con Fionn, ma farò un ulteriore passo avanti e dirò che per me la Pagina non è una risorsa, è una proprietà della richiesta. Questo mi fa scegliere solo la stringa di query dell'opzione 1. Sembra giusto. Mi piace molto come l' API di Twitter sia strutturata in modo riposante. Non troppo semplice, non troppo complicato, ben documentato. Nel bene o nel male, è il mio progetto "vai a" quando sono sulla recinzione a fare qualcosa in un modo contro un altro.


28
+1: le stringhe di query non sono identificatori di risorse di prima classe; hanno solo chiarimenti per l'ordinamento e il raggruppamento della risorsa.
S.Lott

1
@ S.Lott La richiesta è la risorsa. Le cosiddette "risorse di prima classe" sono definite come valori da Fielding nella sezione 5.2.1.1 della sua tesi di laurea . Inoltre, nella stessa sezione, Fielding fornisce l' ultima revisione di un file di codice sorgente come esempio di una risorsa. Come può essere una risorsa ma gli ultimi 10 prodotti possono essere "proprietà della richiesta sulla risorsa prodotti"? Capisco che la tua opinione sia più pratica, ma penso che sia meno RESTful.
edsioufi

Nota che il mio commento non significa che non sono d'accordo con la scelta di utilizzare stringhe di query su URL: entrambe sono soluzioni praticabili a condizione che l'API sia guidata da hypermedia, come ha menzionato @RichApodaca nella sua risposta. Sto solo sottolineando che la Pagina dovrebbe essere considerata una risorsa dal punto di vista REST.
edsioufi,

37

HTTP ha un'intestazione Range estesa, adatta anche per l'impaginazione. È possibile inviare

Range: pages=1

per avere solo la prima pagina. Ciò potrebbe costringerti a ripensare a ciò che è una pagina. Forse il cliente desidera una gamma diversa di articoli. L'intestazione dell'intervallo funziona anche per dichiarare un ordine:

Range: products-by-date=2009_03_27-

per ottenere tutti i prodotti più recenti di quella data o

Range: products-by-date=0-2009_11_30

per ottenere tutti i prodotti più vecchi di quella data. '0' non è probabilmente la soluzione migliore, ma RFC sembra voler qualcosa per l'avvio della gamma. Potrebbero essere distribuiti parser HTTP che non analizzerebbero le unità = -range_end.

Se le intestazioni non sono un'opzione (accettabile), ritengo che la prima soluzione (tutto nella stringa di query) sia un modo per gestire le pagine. Ma per favore, normalizza le stringhe di query (ordina (chiave = valore) coppie in ordine alfabetico). Questo risolve il problema di differenziazione "? A = 1 & b = x" e "? B = x & a = 1".


34
le intestazioni potrebbero sembrare belle a prima vista, ma non consentono la condivisione della pagina (ad esempio copiando l'URL). Quindi, per una richiesta Ajax potrebbero essere una buona soluzione (dal momento che le pagine modificate da Ajax non possono essere condivise nel loro stato attuale comunque), ma non le userei per l'impaginazione regolare.
Markus,

3
E l'intestazione Range è solo per intervalli di byte. Vedere [la specifica delle intestazioni HTTP] ( w3.org/Protocols/rfc2616/rfc2616-sec14.html ), sezione 14.35.
Chris Westin,

16
@ChrisWestin w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.12 HTTP / 1.1 utilizza le unità di intervallo nei campi di intestazione Range (sezione 14.35) e Content-Range (sezione 14.16). range-unit = bytes-unit | other-range-unit Forse ti riferisci a The only range unit defined by HTTP/1.1 is "bytes". HTTP/1.1 implementations MAY ignore ranges specified using other units.Non è lo stesso della tua affermazione.
temoto,

1
@Markus Non riesco a immaginare il caso d'uso quando condividi la risorsa API di riposo :)
JakubKnejzlik,

@JakubKnejzlik La condivisione non è un problema, ma l'utilizzo delle intestazioni HTTP per il paging impedisce l'utilizzo dei collegamenti HATEOAS per il paging.
xarx,

25

L'opzione 1 sembra la migliore, nella misura in cui l'applicazione vede l'impaginazione come una tecnica per produrre una vista diversa della stessa risorsa.

Detto questo, lo schema URL è relativamente insignificante. Se stai progettando che la tua applicazione sia guidata dall'ipertesto (come tutte le applicazioni REST devono essere per definizione), il tuo client non costruirà alcun URI da solo. Invece, l'applicazione fornirà i collegamenti al client e il client li seguirà.

Un tipo di collegamento che il cliente può fornire è un collegamento di impaginazione.

Il piacevole effetto collaterale di tutto ciò è che anche se cambi idea sulla struttura dell'URI di impaginazione e implementi qualcosa di completamente diverso la prossima settimana, i tuoi clienti possono continuare a lavorare senza alcuna modifica.


3
Bel promemoria sull'uso di collegamenti come hypermedia nei servizi web REST.
Paul D. Eden,

11

Ho sempre usato lo stile dell'opzione 1. La memorizzazione nella cache non è stata una preoccupazione poiché i dati cambiano spesso nel mio caso. Se si consente di configurare la dimensione della pagina, i dati non possono essere nuovamente memorizzati nella cache.

Non trovo l'url difficile da ricordare o impuro. Per me questo è un ottimo uso dei parametri della query. La risorsa è chiaramente un elenco di prodotti e i parametri della query indicano semplicemente come si desidera visualizzare l'elenco, ordinare e quale pagina.


1
+1 Penso che tu abbia ragione e vado con i parametri della query (opzione 1)
andi

"Non trovo l'URL difficile da ricordare". Questa osservazione è inutile nelle applicazioni REST, poiché quelle dovrebbero in genere avere un solo segnalibro ... Se un utente (o un'app client) prova a "ricordare" l'URL, questo è un buon segno che l'API non è riposante.
edsioufi,

8

Strano che nessuno abbia sottolineato che l'opzione 3 ha parametri in un ordine specifico. http // application / products / Date / Descending / Name / Ascending / page / 2 e http // application / products / Name / Ascending / Date / Descending / page / 2

stanno puntando alla stessa risorsa, ma hanno URL completamente diversi.

Per me l'opzione 1 sembra la più accettabile, dal momento che separa chiaramente "Cosa voglio" e "Come voglio" (Ha anche un punto interrogativo tra loro lol). La memorizzazione nella cache a pagina intera può essere implementata utilizzando l'URL completo (Tutte le opzioni soffriranno comunque dello stesso problema).

Con l'approccio Parametri nell'URL l'unico vantaggio è l'URL pulito. Anche se devi trovare un modo per codificare i parametri e decodificarli senza perdita di dati. Ovviamente puoi andare con URLencode / decodifica, ma renderà di nuovo brutti gli URL :)


1
Sono due ordini diversi. Il primo ordina per data decrescente e rompe solo i legami per nome crescente; il secondo ordina per nome crescente e rompe i legami solo per data decrescente.
Imran Rashid,

In effetti i due URL di esempio forniti qui non sono diversi solo per la scrittura, ma anche per significato. Dal momento che indica un percorso, non viene data alcuna garanzia di trovare la stessa cosa quando si gira a sinistra prima e subito dopo o viceversa. Detto questo, i parametri di ordinamento come parti del percorso dell'URL presentano vantaggi formali rispetto ai parametri dell'URL che dovrebbero essere scambiabili commutativamente senza cambiare il significato generale, ma in effetti soffrono di trappole di codifica come si dice qui.
Christian Gosch,

7

Preferirei utilizzare l'offset e il limite dei parametri della query.

offset : per l'indice dell'articolo nella collezione.

limite : per il conteggio degli articoli.

Il client può semplicemente continuare ad aggiornare l'offset come segue

offset = offset + limit

per la pagina successiva.

Il percorso è considerato l'identificatore di risorsa. E una pagina non è una risorsa ma un sottoinsieme della raccolta di risorse. Poiché l'impaginazione è generalmente una richiesta GET, i parametri di query sono più adatti per l'impaginazione piuttosto che per le intestazioni.

Riferimento: https://metamug.com/article/rest-api-developers-dilemma.html#Requesting-the-next-page


5

Alla ricerca delle migliori pratiche mi sono imbattuto in questo sito:

http://www.restapitutorial.com

Nella pagina delle risorse c'è un link per scaricare un .pdf che contiene le migliori pratiche REST complete suggerite dall'autore. In cui tra l'altro c'è una sezione sulla paginazione.

L'autore suggerisce di aggiungere supporto sia usando un'intestazione Range sia usando i parametri della stringa di query.

Richiesta

Esempio di intestazione HTTP:

Range: items=0-24

Esempio di parametri della stringa di query:

GET http://api.example.com/resources?offset=0&limit=25

Dove offset è il numero di articolo iniziale e limite è il numero massimo di articoli da restituire.

Risposta

La risposta dovrebbe includere un'intestazione di Content-Range che indica quanti articoli vengono restituiti e quanti elementi totali esistono ancora da recuperare

Esempi di intestazione HTTP:

Content-Range: items 0-24/66

Content-Range: items 40-65/*

Nel .pdf ci sono altri suggerimenti per casi più specifici.


4

Attualmente sto usando uno schema simile a questo nelle mie app ASP.NET MVC:

per esempio http://application/products/by-date/page/2

in particolare è: http://application/products/Date/Ascending/3

Tuttavia, non sono molto contento di includere in questo modo le informazioni di paginazione e ordinamento nel percorso.

L'elenco degli articoli (prodotti in questo caso) è modificabile. cioè la prossima volta che qualcuno ritorna a un url che include parametri di paginazione e ordinamento, i risultati che ottengono potrebbero essere cambiati. Quindi l'idea di http://application/products/Date/Ascending/3come un url unico che punta a un insieme definito e immutabile di prodotti è persa.


1
A mio avviso, il primo problema, con l'ordinamento su più colonne, si applica a tutti e 3 i metodi. Quindi non è davvero un pro / contro per nessuno di loro. Per quanto riguarda il secondo problema: questo non può accadere a nessuna risorsa? Un prodotto, ad esempio, può anche essere modificato / eliminato.
andi

Penso che l'ordinamento su più colonne sia davvero un 'contro' per tutti e 3 i metodi poiché l'URL diventa più grande e più ingestibile, quindi uno dei motivi per cui sto prendendo in considerazione l'idea di passare a parametri di pagina / ordinamento basati su moduli. Per il secondo numero, penso che ci sia una differenza concettuale fondamentale tra un identificatore univoco persistente come un ID prodotto rispetto a un elenco temporaneo di prodotti. Per i prodotti eliminati un messaggio, ad esempio "Quel prodotto non esiste nel sistema", ti dice qualcosa di concreto su quel prodotto.
Steve Willcock,

1
La rimozione di tutte le informazioni di paginazione e ordinamento dal percorso è buona. E spingerlo nei parametri POST è male. Ciao? La domanda riguarda REST. Non stiamo usando POST solo per ridurre l'URL in REST. Il verbo ha un senso.
temoto,

1
Personalmente, non userei i parametri del modulo per una query perché richiederebbe quasi un metodo POST o PUT HTTP (poiché ora c'è un corpo nella richiesta). GET mi sembra il metodo più appropriato da utilizzare poiché sia ​​POST che PUT implicano la modifica della risorsa. A causa di ciò vorrei aggiungere ulteriori parametri di query all'URL quando è necessario l'ordinamento per più colonne.
Paul D. Eden,

1

Tendo a concordare con slf che "pagina" non è davvero una risorsa. D'altra parte, l'opzione 3 è più pulita, più facile da leggere e può essere più facilmente indovinata dall'utente e può anche essere digitata, se necessario. Sono diviso tra le opzioni 1 e 3, ma non vedo alcun motivo per non utilizzare l'opzione 3.

Inoltre, sebbene abbiano un bell'aspetto, un aspetto negativo dell'utilizzo di parametri nascosti, come qualcuno ha menzionato, piuttosto che stringhe di query o segmenti di URL è che l'utente non può aggiungere un segnalibro o collegarsi direttamente a una determinata pagina. Questo può essere o meno un problema a seconda dell'applicazione, ma solo qualcosa di cui essere a conoscenza.


1
Per quanto riguarda la tua menzione di essere più facile da indovinare, questo non dovrebbe importare. Se si crea un'API hypermedia, gli utenti non DEVONO mai indovinare gli URI.
JR Garcia,

0

Ho già usato la soluzione 3 (scrivo MOLTE app django). E non penso che ci sia qualcosa di sbagliato in questo. È altrettanto generabile come gli altri due (nel caso in cui sia necessario eseguire raschietti di massa o simili) e sembra più pulito. Inoltre, i tuoi utenti possono indovinare gli URL (se si tratta di un'app pubblica) e alle persone piace essere in grado di andare direttamente dove vogliono, e l'ipotesi di url si sente potenziante.


0

Uso nei miei progetti i seguenti URL:

http://application/products?page=2&sort=+field1-field2

che significa - "dammi la pagina la seconda pagina ordinata in ordine crescente per campo1 e poi decrescente per campo2". O se ho bisogno di una flessibilità ancora maggiore, utilizzo:

http://application/products?skip=20&limit=20&sort=+field1-field2

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.