Qual è il miglior metodo RESTful per restituire il numero totale di elementi in un object?

Sto sviluppando un servizio API REST per un grande sito di social networking in cui sono coinvolto. Finora, sta funzionando alla grande. Posso inviare richieste GET , POST , PUT e DELETE agli URL degli oggetti e influire sui miei dati. Tuttavia, questi dati sono paginati (limitati a 30 risultati alla volta).

Tuttavia, quale sarebbe il miglior modo RESTful per ottenere il numero totale di membri, tramite la mia API?

Attualmente, invio richieste a una struttura di URL come la seguente:

  • / api / membri -Ripresenta una lista di membri (30 alla volta come menzionato sopra)
  • / api / members / 1 -Affects a single member, in base al metodo di richiesta utilizzato

La mia domanda è: come utilizzerei una struttura URL simile per ottenere il numero totale di membri nella mia applicazione? Ovviamente richiedere solo il campo id (simile all’API Graph di Facebook) e il conteggio dei risultati sarebbe inefficace dato che solo una porzione di 30 risultati verrebbe restituita.

    Mentre la risposta a / API / utenti è paginata e restituisce solo 30, record, non c’è nulla che ti impedisce di includere nella risposta anche il numero totale di record e altre informazioni rilevanti, come la dimensione della pagina, il numero di pagina / offset, ecc .

    L’API StackOverflow è un buon esempio di quello stesso design. Ecco la documentazione per il metodo Utenti: https://api.stackexchange.com/docs/users

    Preferisco usare le intestazioni HTTP per questo tipo di informazioni contestuali.

    Per il numero totale di elementi uso l’intestazione X-total-count .
    Per i collegamenti alla pagina successiva, precedente, ecc. Uso l’intestazione di Link http:
    http://www.w3.org/wiki/LinkHeader

    Github lo fa allo stesso modo: https://developer.github.com/v3/#pagination

    Secondo me è più pulito in quanto può essere utilizzato anche quando restituisci contenuti che non supportano collegamenti ipertestuali (es. Binari, immagini).

    Ultimamente ho svolto ricerche approfondite su questo e altre domande relative al paging REST e ho ritenuto utile aggiungere alcune delle mie conclusioni qui. Sto espandendo un po ‘la domanda per includere i pensieri sul paging e il conteggio in quanto sono strettamente correlati.

    intestazioni

    I metadati di paging sono inclusi nella risposta sotto forma di intestazioni di risposta. Il grande vantaggio di questo approccio è che il carico utile della risposta è di per sé solo l’attuale richiedente dei dati. Rendere l’elaborazione della risposta più semplice per i clienti che non sono interessati alle informazioni di paging.

    Ci sono un sacco di intestazioni (standard e personalizzate) utilizzate in natura per restituire informazioni relative alla paginazione, incluso il conteggio totale.

    X-Total-Count

     X-Total-Count: 234 

    Questo è usato in alcune API che ho trovato in natura. Esistono anche pacchetti NPM per aggiungere il supporto per questa intestazione ad es. Loopback. Alcuni articoli consigliano di impostare anche questa intestazione.

    Viene spesso utilizzato in combinazione con l’intestazione Link , che è una soluzione piuttosto buona per il paging, ma manca delle informazioni sul conteggio totale.

    collegamento

     Link: ; rel="previous"; title*=UTF-8'de'letztes%20Kapitel, ; rel="next"; title*=UTF-8'de'n%c3%a4chstes%20Kapitel 

    Sento, leggendo molto su questo argomento, che il consenso generale è di usare l’ intestazione Link per fornire collegamenti di paging ai client usando rel=next , rel=previous ecc. Il problema con questo è che manca l’informazione di quanti i record totali ci sono, motivo per cui molte API combinano questo con l’intestazione X-Total-Count .

    In alternativa, alcune API e, ad esempio, lo standard JsonApi , utilizzano il formato Link , ma aggiungono le informazioni in una busta di risposta anziché in un’intestazione. Ciò semplifica l’accesso ai metadati (e crea una posizione per aggiungere le informazioni di conteggio totale) a spese della crescente complessità dell’accesso ai dati effettivi stessi (aggiungendo una busta).

    Content-Range

     Content-Range: items 0-49/234 

    Promosso da un articolo del blog chiamato Range header, ho scelto te (per impaginazione)! . L’autore fa un buon esempio dell’uso delle intestazioni Range e Content-Range per l’impaginazione. Quando leggiamo attentamente l’ RFC su questi header, scopriamo che estendere il loro significato oltre i range di byte era in realtà anticipato dalla RFC ed è esplicitamente permesso. Quando viene utilizzato nel contesto di items anziché bytes , l’intestazione Intervallo ci offre effettivamente un modo per richiedere entrambi un certo intervallo di elementi e indicare quale intervallo del risultato totale si riferisce agli elementi di risposta. Questa intestazione offre anche un ottimo modo per mostrare il conteggio totale. Ed è un vero standard che per lo più esegue il mapping uno a uno al paging. Viene anche usato in natura .

    Busta

    Molte API, inclusa quella del nostro sito Web di domande e risposte preferite, utilizzano una busta , un involucro attorno ai dati che vengono utilizzati per aggiungere informazioni sui metadati sui dati. Inoltre, gli standard OData e JsonApi usano entrambi una busta di risposta.

    Il grosso svantaggio di questo (imho) è che l’elaborazione dei dati di risposta diventa più complessa in quanto i dati effettivi devono essere trovati da qualche parte nella busta. Inoltre ci sono molti formati diversi per quella busta e devi usare quello giusto. Sta dicendo che gli inviluppi di risposta di OData e JsonApi sono molto diversi, con OData che si mescola nei metadati in più punti della risposta.

    Endpoint separato

    Penso che questo sia stato trattato abbastanza nelle altre risposte. Non ho investigato molto perché sono d’accordo con i commenti sul fatto che questo è fonte di confusione poiché ora avete più tipi di endpoint. Penso che sia più bello se ogni endpoint rappresenta una (collezione di) risorsa (s).

    Ulteriori pensieri

    Non dobbiamo solo comunicare le meta informazioni di paging relative alla risposta, ma anche consentire al cliente di richiedere pagine / intervalli specifici. È interessante anche guardare questo aspetto per finire con una soluzione coerente. Anche qui possiamo usare le intestazioni (l’intestazione Range sembra molto adatta) o altri meccanismi come i parametri di query. Alcune persone sostengono di trattare le pagine dei risultati come risorse separate, il che può avere senso in alcuni casi d’uso (ad esempio /books/231/pages/52 Ho finito per selezionare una gamma di parametri di richiesta utilizzati frequentemente come pagesize , page[size] e limit ecc. oltre a supportare l’intestazione Range (e anche il parametro request).

    È ansible restituire il conteggio come intestazione HTTP personalizzata in risposta a una richiesta HEAD. In questo modo, se un cliente desidera solo il conteggio, non è necessario restituire l’elenco effettivo e non è necessario un URL aggiuntivo.

    (Oppure, se ti trovi in ​​un ambiente controllato dall’endpoint all’endpoint, potresti utilizzare un verbo HTTP personalizzato come COUNT.)

    Alternativa quando non hai bisogno di oggetti reali

    La risposta di Franci Penov è sicuramente il modo migliore per fare in modo che tu restituisca sempre gli articoli insieme a tutti i metadati aggiuntivi sulle tue quadro richieste. Questo è il modo in cui dovrebbe essere fatto.

    ma a volte restituire tutti i dati non ha senso, perché potresti non averne affatto bisogno. Forse tutto ciò di cui hai bisogno sono i metadati sulla tua risorsa richiesta. Come il conteggio totale o il numero di pagine o qualcos’altro. In tal caso puoi sempre avere una query URL per dire al tuo servizio di non restituire gli articoli ma piuttosto solo metadati come:

     /api/members?metaonly=true /api/members?includeitems=0 

    o qualcosa di simile …

    Consiglierei l’aggiunta di intestazioni per lo stesso, ad esempio:

     HTTP/1.1 200 Pagination-Count: 100 Pagination-Page: 5 Pagination-Limit: 20 Content-Type: application/json [ { "id": 10, "name": "shirt", "color": "red", "price": "$23" }, { "id": 11, "name": "shirt", "color": "blue", "price": "$25" } ] 

    Per dettagli fare riferimento a:

    https://github.com/adnan-kamili/rest-api-response-format

    Per il file swagger:

    https://github.com/adnan-kamili/swagger-response-template

    Che dire di un nuovo endpoint> / api / members / count che chiama semplicemente Members.Count () e restituisce il risultato

    Sembra più facile aggiungere solo un

     GET /api/members/count 

    e restituire il conteggio totale dei membri

    A volte i framework (come $ resource / AngularJS) richiedono un array come risultato di una query, e non si può avere una risposta come {count:10,items:[...]} in questo caso io memorizzo “count” in responseHeaders .

    PS In realtà puoi farlo con $ resource / AngularJS, ma ha bisogno di alcune modifiche.

    Quando si richiedono dati impaginati, si conosce (tramite il valore del parametro della dimensione della pagina esplicito o il valore della dimensione della pagina predefinita) la dimensione della pagina, in modo da sapere se tutti i dati sono stati ricevuti o meno. Quando nella risposta c’è meno dati di quante dimensioni ha una pagina, allora hai dati interi. Quando viene restituita una pagina intera, devi chiedere di nuovo un’altra pagina.

    Preferisco avere un endpoint separato per il conteggio (o lo stesso endpoint con il parametro countOnly). Perché è ansible preparare l’utente finale per un processo lungo / dispendioso visualizzando la barra di avanzamento correttamente avviata.

    Se si desidera restituire il datasize in ogni risposta, dovrebbe esserci pageSize, offset anche menzionato. Per essere onesti, il modo migliore è ripetere anche i filtri di richiesta. Ma la risposta è diventata molto complessa. Quindi, preferisco l’endpoint dedicato per il conteggio di ritorno.

                  

    Il mio couleage preferisce un parametro countOnly all’endpoint esistente. Quindi, quando specificato, la risposta contiene solo metadati.

    endpoint? filter = valore

         ...   

    endpoint? filter = value & countOnly = true