REST Complex / Composite / Nested Resources [chiuso]


177

Sto cercando di avvolgere la mia testa intorno al modo migliore per affrontare i concetti in un'API basata su REST. Le risorse piatte che non contengono altre risorse non sono un problema. Dove sto correndo nei guai sono le risorse complesse.

Ad esempio, ho una risorsa per un fumetto. ComicBookha tutti i tipi di proprietà su di esso come author, issue number, date, etc.

Un fumetto ha anche un elenco di 1..ncopertine. Queste copertine sono oggetti complessi. Contengono molte informazioni sulla copertina: l'artista, una data e persino un'immagine codificata di base 64 della copertina.

Per un GETon ComicBookho potuto solo tornare il comico, e tutte le copertine comprese le loro immagini base64'ed. Questo non è probabilmente un grosso problema per ottenere un singolo fumetto. Ma supponiamo che stia creando un'app client che vuole elencare tutti i fumetti nel sistema in una tabella.
La tabella conterrà alcune proprietà della ComicBookrisorsa, ma certamente non vorremmo visualizzare tutte le copertine nella tabella. Restituendo 1000 fumetti, ognuno con più copertine si tradurrebbe in una quantità ridicolmente grande di dati che attraversano il filo, dati che non sono necessari all'utente finale in quel caso.

Il mio istinto è quello di creare Coveruna risorsa e ComicBookcontenere copertine. Quindi ora Coverè un URI. GETsui fumetti funziona ora, invece dell'enorme Coverrisorsa che inviamo indietro un URI per ogni copertina e i clienti possono recuperare le risorse della copertina quando le richiedono.

Ora ho un problema con la creazione di nuovi fumetti. Sicuramente ho intenzione di creare almeno una copertina quando creo un Comic, in realtà è probabilmente una regola aziendale.
Quindi ora sono bloccato, o costringo i clienti a far rispettare le regole di business inviando prima un Cover, ottenendo l'URI per quella copertura, quindi POSTinserendo un ComicBookcon quell'URI nell'elenco o il mio POSTon ComicBookaccetta una risorsa dall'aspetto diverso da quello che sputa su. Le risorse in entrata per POSTe GETsono copie profonde, in cui le uscite in uscita GETcontengono riferimenti a risorse dipendenti.

La Coverrisorsa è probabilmente necessaria in ogni caso perché sono sicuro che come cliente vorrei indirizzare la direzione delle copertine in alcuni casi. Quindi il problema esiste in una forma generale indipendentemente dalle dimensioni della risorsa dipendente. In generale, come gestite risorse complesse senza forzare il cliente a "sapere" come sono composte tali risorse?


ha senso utilizzare RESTFUL SERVICE DISCOVERY ?
treecoder,

1
Sto cercando di aderire a HATEAOS che, a mio avviso, è contrario all'utilizzo di qualcosa del genere, ma darò un'occhiata.
jgerman,

Domanda diversa nello stesso spirito. Tuttavia, la proprietà è diversa dalla soluzione proposta (quella nella domanda). stackoverflow.com/questions/20951419/…
Wes

Risposte:


64

@ray, eccellente discussione

@jgerman, non dimenticare che solo perché è REST, non significa che le risorse debbano essere messe in pietra da POST.

Ciò che scegli di includere in una determinata rappresentazione di una risorsa dipende da te.

Il tuo caso delle copertine a cui si fa riferimento separatamente è semplicemente la creazione di una risorsa padre (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 creare nuove risorse secondarie al momento della creazione della risorsa principale.

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 risorsa e l'indirizzo mittente come risorsa figlio, è ovviamente possibile fare riferimento separatamente all'indirizzo mittente. Ad esempio, ottieni tutto dagli indirizzi. In alternativa, crea un nuovo messaggio con un precedente indirizzo da. Se l'e-mail fosse REST, si potrebbe facilmente vedere che molte risorse con riferimenti incrociati potrebbero essere disponibili: / messaggi-ricevuti, / bozze-messaggi, / da-indirizzi, / a-indirizzi, / indirizzi, / oggetti, / allegati, / cartelle , / tags, / categorie, / labels, 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 pubblichi un URI, un ID o una data di creazione per la nuova risorsa, poiché questi sono generati dal server. Eppure, puoi recuperare l'URI, l'ID o la data di creazione quando recuperi 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 padre, è possibile rappresentare tali risorse figlio come gli stessi dati binari o come URI che rappresentano i dati binari.

Forme e parametri sono già diversi dalle rappresentazioni HTML delle risorse. Pubblicare un parametro binario / file che si traduce 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 della risorsa relativa ai moduli. Se lo pubblichi nella raccolta di risorse con il tipo di contenuto "application / x-www-form-urlencoded" o "multipart / form-data", stai chiedendo al server di salvare la rappresentazione di quel tipo. Il server può rispondere con la rappresentazione HTML salvata o qualsiasi altra cosa.

È possibile consentire anche la pubblicazione di una rappresentazione HTML, XML o JSON nella raccolta di risorse, a scopo di API o simili.

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

  • Consente la creazione ritardata della copertina
  • Consente la creazione di fumetti con la copertina richiesta
  • Consente alle copertine di essere incrociate
  • Consente più copertine
  • Crea bozze di fumetti
  • Crea bozze di copertine di fumetti
  • Pubblica bozza di fumetto

OTTIENI / fumetti
=> 200 OK, ricevi tutti i fumetti.

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

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

OTTIENI / copertine
=> 200 OK, ottieni tutte le copertine.

OTTIENI / covers / 1
=> 200 OK, ottieni la cover (id: 1) con il fumetto (/ comic-books / 0).

GET / comic-books / new
=> 200 OK, Ottieni modulo per creare fumetti (modulo: POST / draft-comic-books).

POST / draft-comic-books
title = pippo
autore = boo
editore = goo
pubblicato = 2011-01-01
=> 302 trovato, posizione: / draft-comic-books / 3, reindirizzare alla bozza di fumetti (id: 3) con copertine (binarie).

OTTIENI / bozze-fumetti / 3
=> 200 OK, ottieni bozze di fumetti (id: 3) con copertine.

OTTIENI / bozze di fumetti / 3 / copertine
=> 200 OK, ottieni copertine per bozze di fumetti (/ bozze di fumetti / 3).

GET / draft-comic-books / 3 / covers / new
=> 200 OK, Ottieni modulo per creare una copertina per bozza di fumetto (/ draft-comic-book / 3) (modulo: POST / draft-comic-books / 3 / copertine).

POST / draft-comic-books / 3 / covers
cover_type = front
cover_data = (binary)
=> 302 Found, Ubicazione: / draft-comic-books / 3 / covers, Reindirizzamento alla nuova copertina per draft comic book (/ draft-comic -Book / 3 / coperchi / 1).

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

POST /
titoli -libri-pubblicati =
autore foo =
editore boo = goo
pubblicato =
01-01-2011 cover_type = front
cover_data = (binario)
=> 302 trovati, posizione: / comic-books / 3, reindirizza al fumetto pubblicato (id: 3) con copertine.


Sono un principiante per questo, e sto cercando di impararlo in fretta. L'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) sarebbe disapprovato. Quindi non dovrebbe essere POST / draft-comic-books / 3 / publishing?
Gary McGill,

3
@GaryMcGill Nel suo esempio, / draft-comic-books / 3 / publishing restituisce solo un modulo HTML (non modifica alcun dato).
Olivier Lalonde,

@Olivier è corretto. La parola pubblica è lì per indicare cosa fa il modulo. Tuttavia, poiché vuoi mantenere i verbi confinati ai metodi HTTP, dovresti pubblicare su una risorsa per i fumetti pubblicati. ... Se si trattasse di un sito Web, potrebbe essere necessario un URI affinché il modulo pubblichi qualcosa. ... Sebbene, se l'azione di pubblicazione fosse semplicemente un singolo pulsante nella pagina dei fumetti, tale modulo a singolo pulsante potrebbe pubblicare direttamente nell'URI / comic-books.
Alex,

@Alex, nella richiesta POST restituirei invece una 201 creata, con l'URL della nuova risorsa come posizione nelle intestazioni della risposta.
Ismriv,

2
@Stephane, i reindirizzamenti rendono tutto più semplice per i controller. Anche per un'API, è più semplice far sì che il controller di creazione restituisca la posizione per il nuovo contenuto e quindi consenta al controller di show di gestire la visualizzazione del nuovo contenuto. Tuttavia, è più semplice / più semplice per il client dell'API ottenere il contenuto e non preoccuparsi dei reindirizzamenti.
Alex

45

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

La tua domanda è come gestire la creazione di fumetti. Se la tua regola commerciale era che un libro avrebbe avuto 0 o più copertine, allora non avrai problemi:

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

con i dati dei fumetti senza copertina creerà un nuovo fumetto e restituirà l'id generato dal server (diciamo che torna come 8), e ora puoi aggiungere copertine ad esso in questo modo:

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

con la copertura nel corpo dell'entità.

Ora hai una buona domanda che è cosa succede se la tua regola aziendale dice che ci deve sempre essere almeno una copertura. Ecco alcune scelte, la prima delle quali hai identificato nella tua domanda:

  1. Forzare prima la creazione di una copertina, ora essenzialmente facendo della copertina una risorsa non dipendente, oppure posizionando la copertina iniziale nel corpo dell'entità del POST che crea il fumetto. Questo come dici significa che la rappresentazione che POST crei differirà dalla rappresentazione che OTTIENI.

  2. Definire il concetto di copertina primaria, iniziale, preferita o altrimenti designata. Questo è probabilmente un modellismo, e se lo facessi sarebbe come modificare il tuo modello a oggetti (il tuo modello concettuale o di business) per adattarsi a una tecnologia. Non è una grande idea.

Dovresti soppesare queste due scelte contro il semplice consentire fumetti senza copertina.

Quale delle tre scelte dovresti prendere? Non sapendo troppo della tua situazione, ma rispondi alla domanda generale sulle risorse dipendenti 1..N, direi:

  • Se puoi scegliere 0..N per il tuo livello di servizio RESTful, fantastico. Forse uno strato tra la SOA RESTful può gestire l'ulteriore vincolo aziendale se ne è richiesto almeno uno. (Non sono sicuro di come sarebbe, ma potrebbe valere la pena esplorarlo .... Gli utenti finali di solito non vedono comunque la SOA.)

  • Se devi semplicemente modellare un vincolo 1..N, chiediti 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 hai bisogno di 1..N e le copertine rimangono dipendenti, rilassa semplicemente il tuo istinto per mantenere le rappresentazioni in POST e OTTENERE uguali o renderle uguali.

L'ultimo elemento è 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 POST accetti l'uris esistente se li hai (presi in prestito da altri libri) ma inserisci anche 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 OTTIENI puoi restituire gli URI ..

Quindi fondamentalmente stai permettendo alle rappresentazioni POST e GET di "essere uguali" ma hai semplicemente scelto di non "usare" l'immagine di copertina su GET né la copertina su POST. Spero che abbia un senso.

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.