2011-08-18 12 views
154

Sto cercando di comprendere il modo migliore per affrontare i concetti in un'API basata su REST. Risorse piatte che non contengono altre risorse non sono un problema. Dove mi trovo nei guai sono le risorse complesse.Complesso REST/Composite/Nested Resources

Ad esempio, ho una risorsa per ComicBook. ComicBook ha tutti i tipi di proprietà su di esso come autore, numero di rilascio, data, ecc.

Un fumetto include anche un elenco di copertine 1..n. Queste coperture sono oggetti complessi. Contengono molte informazioni sulla copertina, sull'artista, sulla data e persino su un'immagine codificata di base 64 della copertina.

Per un GET su ComicBook, potevo semplicemente restituire il fumetto e tutte le copertine incluse le immagini di base64. Questo probabilmente non è un grosso problema per ottenere un singolo fumetto. Ma supponiamo che sto costruendo un'app client che vuole elencare tutti i fumetti nel sistema in una tabella. La tabella conterrà alcune proprietà della risorsa ComicBook, ma certamente non vorremmo visualizzare tutte le copertine nella tabella. Restituire 1000 fumetti, ciascuno con più copertine, si tradurrebbe in una quantità ridicolmente grande di dati che arrivano attraverso il filo, dati che non sono necessari all'utente finale in quel caso.

Il mio istinto è quello di rendere Cover una risorsa e ComicBook contiene cover. Quindi ora Cover è un URI. RICEVI ora sui fumetti, invece della grossa risorsa Cover restituiamo un URI per ogni copertina e i clienti possono recuperare le risorse di Cover quando ne hanno bisogno.

Ora ho un problema con la creazione di nuovi fumetti. Sicuramente ho intenzione di creare almeno una copertina quando creo un fumetto, infatti è probabilmente una regola aziendale. Così ora sono bloccato, o costringo i clienti a far rispettare le regole di business inviando prima una cover, ottenendo l'URI per quella copertina, poi postando un ComicBook con quell'URI nella lista, o il mio POST su ComicBook ha un aspetto diverso Risorsa di quanto sputa fuori. Le risorse in entrata per POST e GET sono copie profonde, in cui le GET in uscita contengono riferimenti a risorse dipendenti.

La risorsa di copertura è probabilmente necessaria in ogni caso perché sono sicuro che come cliente vorrei in alcuni casi indirizzare la direzione di Covers. Quindi il problema esiste in una forma generale indipendentemente dalla dimensione della risorsa dipendente. In generale, come gestisci le risorse complesse senza costringere il cliente a "sapere" come sono composte tali risorse?

+0

utilizza [RESTFUL SERVICE DISCOVERY] (http://barelyenough.org/blog/2008/01/restful-service-discovery-and-description/) ha senso? – treecoder

+1

Sto cercando di aderire a HATEAOS che, a mio parere, è contrario all'uso di qualcosa del genere ma darò un'occhiata. – jgerman

+0

Domanda diversa con lo stesso spirito. Tuttavia la proprietà è diversa dalla soluzione proposta (quella nella domanda). http://stackoverflow.com/questions/20951419/what-are-best-practices-for-rest-nested-resources – Wes

risposta

37

Trattare coperture come risorse è sicuramente nello spirito di REST, in particolare HATEOAS. Quindi sì, una richiesta GET a http://example.com/comic-books/1 ti darebbe una rappresentazione del libro 1, con proprietà che includono un set di URI per le copertine. Fin qui tutto bene.

La tua domanda è come gestire la creazione di fumetti. Se la regola aziendale era che un libro avrebbe 0 o più coperture, allora non avete problemi:

POST http://example.com/comic-books 

con i dati dei fumetti Coverless creeranno un nuovo libro di fumetti e di ritorno del server generato id (diciamo ritorna come 8), e ora puoi aggiungere copertine in questo modo:

POST http://example.com/comic-books/8/covers 

con la copertina nel corpo dell'entità.

Ora hai una buona domanda che è quello che succede se la tua regola aziendale dice che deve esserci sempre almeno una copertura.Qui ci sono alcune scelte, la prima delle quali avete identificato nella sua domanda:

  1. imporre la creazione di una copertura prima, ora facendo essenzialmente coprire una risorsa non-dipendente, o si posiziona il coperchio iniziale nel corpo dell'entità del POST che crea il fumetto. Questo come dici tu significa che la rappresentazione che POST crei differirà dalla rappresentazione che ottieni.

  2. Definire la nozione di copertina primaria, iniziale, preferita o altrimenti designata. Questo è probabilmente un trucco da modellazione, e se lo facessi sarebbe come modificare il tuo modello di oggetto (il tuo modello concettuale o di business) per adattarlo a una tecnologia. Non è una grande idea.

Si dovrebbe valutare queste due opzioni semplicemente consentendo fumetti senza copertura.

Quale delle tre scelte dovresti prendere? Non sapendo troppo circa la vostra situazione, ma rispondere alla domanda generale 1..N risorsa dipendente, direi:

  • Se si può andare con 0..n per il vostro livello di servizio RESTful, grande. Forse uno strato tra il SOA RESTful può gestire l'ulteriore vincolo di business se ne è richiesto almeno uno. (Non so come sembrerebbe, ma potrebbe valere la pena di essere esplorato.)

  • Se si deve semplicemente modellare un vincolo 1..N, chiedersi se le copertine potrebbero essere solo risorse condivisibili, in altre parole, potrebbero esistere su cose diverse dai fumetti. Ora non sono risorse dipendenti e puoi crearle prima e fornire URI nel tuo POST che crea fumetti.

  • Se è necessario 1..N e le copertine rimangono dipendenti, basta rilassare il proprio istinto per mantenere le rappresentazioni in POST e GET uguali o renderle uguali.

L'ultima voce è spiegato in questo modo:

<comic-book> 
    <name>...</name> 
    <edition>...</edition> 
    <cover-image>...BASE64...</cover-image> 
    <cover-image>...BASE64...</cover-image> 
    <cover>...URI...</cover> 
    <cover>...URI...</cover> 
</comic-book> 

Quando pubblichi si permetterà URI esistente se si dispone di loro (preso in prestito da altri libri), ma anche mettere in una o più immagini iniziali. Se stai creando un libro e la tua entità non ha un'immagine di copertina iniziale, restituisci una risposta 409 o simile. Su GET puoi restituire URI ..

Quindi in pratica stai permettendo alle rappresentazioni POST e GET di "essere lo stesso" ma devi solo scegliere di non "usare" l'immagine di copertina su GET né coprire il POST. Spero che abbia un senso.

58

@ray, eccellente discussione

@jgerman, non dimenticare che solo perché si tratta di REST, non significa che le risorse devono essere impostati in pietra da POST.

Ciò che si sceglie di includere in una determinata rappresentazione di una risorsa dipende da voi.

Il vostro caso delle copertine referenziate separatamente è semplicemente la creazione di una risorsa genitore (fumetto) le cui risorse secondarie (copertine) possono essere incrociate. Ad esempio, potresti anche voler fornire riferimenti ad autori, editori, personaggi o categorie separatamente. Potresti voler creare queste risorse separatamente o prima del fumetto che le fa riferimento come risorse secondarie. In alternativa, potresti voler creare nuove risorse secondarie al momento della creazione della risorsa genitore.

Il tuo caso specifico delle copertine è leggermente più complesso in quanto una copertina richiede davvero un fumetto, e viceversa.

Tuttavia, se si considera un messaggio di posta elettronica come una risorsa e l'indirizzo come risorsa secondaria, è ovviamente possibile fare riferimento all'indirizzo separatamente. Ad esempio, ottieni tutto dagli indirizzi. Oppure crea un nuovo messaggio con un indirizzo precedente. Se la posta elettronica fosse REST, si potrebbe facilmente vedere che molte risorse con riferimenti incrociati potrebbero essere disponibili:/messaggi ricevuti,/bozze-messaggi,/da-indirizzi,/a-indirizzi,/indirizzi,/soggetti,/allegati,/cartelle ,/tag,/categorie,/etichette, et al.

Questo tutorial fornisce un ottimo esempio di risorse con riferimenti incrociati. http://www.peej.co.uk/articles/restfully-delicious.html

Questo è il modello più comune per i dati generati automaticamente. Ad esempio, non inserisci un URI, ID o data di creazione per la nuova risorsa, poiché questi sono generati dal server. Eppure, è possibile recuperare l'URI, l'ID o la data di creazione quando si riapre la nuova risorsa.

Un esempio nel tuo caso di dati binari. Ad esempio, si desidera pubblicare dati binari come risorse secondarie. Quando si ottiene la risorsa genitore, è possibile rappresentare tali risorse secondarie come gli stessi dati binari o come URI che rappresentano i dati binari.

I moduli & sono già diversi dalle rappresentazioni HTML delle risorse. Pubblicare un parametro binario/file che risulta in un URL non è un tratto.

Quando si ottiene il modulo per una nuova risorsa (/ fumetti/nuovo) o si ottiene il modulo per modificare una risorsa (/ fumetti/0/modifica), si richiede una rappresentazione specifica per moduli della risorsa. Se lo si registra nella raccolta di risorse con tipo di contenuto "application/x-www-form-urlencoded" o "multipart/form-data", si chiede al server di salvare la rappresentazione di quel tipo. Il server può rispondere con la rappresentazione HTML che è stata salvata, o qualsiasi altra cosa.

Si consiglia inoltre di consentire una rappresentazione in HTML, XML o JSON da pubblicare nella raccolta di risorse, ai fini di un'API o simili.

È anche possibile rappresentare le risorse e il flusso di lavoro che descrivi, tenendo conto delle copertine pubblicate dopo il fumetto, ma richiedendo fumetti per avere una copertina. Esempio come segue.

  • permette la creazione di copertura ritardato
  • Consente la creazione di fumetti con la necessaria copertura
  • Consente coperte per essere incrociate
  • consente a più coperture
  • Crea progetto a fumetti
  • Crea progetto a fumetti covers
  • Pubblica bozza di fumetti

GET/fumetti
=> 200 OK, ottieni tutti i fumetti.

GET/fumetti/0
=> 200 OK, ottieni fumetti (id: 0) con copertine (/ copertine/1,/copertine/2).

GET/fumetti/0/copertine
=> 200 OK, ottieni copertine per fumetti (id: 0).

GET/cover
=> 200 OK, ottieni tutte le copertine.

GET/covers/1
=> 200 OK, ottenere la copertina (id: 1) con il fumetto (/ fumetti/0).

GET/fumetti/nuovo
=> 200 OK, Ottieni modulo per creare fumetti (modulo: POST/bozza-fumetti-libri).

POST/draft-comic-libri
title = foo
autore = boo
editore = goo
pubblicato = 2011-01-01
=> 302 Trovato, Location:/draft-comic-book/3, Redirect to draft comic book (id: 3) con copertine (binarie).

GET/bozza di fumetti/3
=> 200 OK, Scarica bozza di fumetti (id: 3) con copertine.

GET/bozza di fumetti/3/copertine
=> 200 OK, Ottieni copertine per bozze di fumetti (/ bozza-fumetto-libro/3).

GET/draft-comic-book/3/copre/nuovo
=> 200 OK, Get modulo per creare copertina per progetto a fumetti (/ draft-comic-book/3) (modulo: POST/draft- comic-book/3/coperture).

POST/draft-comic-book/3/copre
cover_type = davanti
cover_data = (binari)
=> 302 Trovato, Luogo:/draft-comic-libri/3/coperture, reindirizzare a nuovo copertina per bozza di fumetti (/ bozza-fumetto/3/copertine/1).

GET/draft-comic-books/3/publish
=> 200 OK, Ottieni modulo per pubblicare bozza di fumetti (id: 3) (modulo: POST/pubblicato-fumetti-libri).

POST/pubblicato-comic-libri
title = foo
autore = boo
editore = goo
pubblicato = 2011-01-01
cover_type = davanti
cover_data = (binario)
= > 302 Found, Location:/comic-books/3, Redirect to comic book (id: 3) con copertine.

+0

Sono un novizio totale a questo, e cerco di impararlo in fretta. Ho trovato estremamente utile. Tuttavia, negli altri blog, ecc. Che ho letto oggi, l'uso di GET per eseguire un'operazione (in particolare un'operazione che non è idempotente) verrebbe disapprovato. Quindi non dovrebbe essere POST/draft-comic-books/3/publish? –

+3

@GaryMcGill Nel suo esempio/draft-comic-books/3/publish restituisce solo un modulo HTML (non modifica alcun dato). –

+0

@Olivier è corretto. La parola pubblica è lì per indicare cosa fa il modulo. Tuttavia, poiché desideri mantenere i verbi confinati ai metodi HTTP, devi pubblicare su una risorsa per i fumetti pubblicati. ... Se questo fosse un sito Web, potrebbe essere necessario un URI per il modulo per pubblicare qualcosa. ... Sebbene, se l'azione di pubblicazione fosse semplicemente un singolo pulsante nella pagina dei fumetti, quel modulo a pulsante singolo potrebbe postare direttamente nell'URI pubblicato/a fumetti. – Alex