2013-10-13 12 views
23

Questa domanda riguarda la progettazione ottimale dell'API REST e un problema che sto affrontando per scegliere tra le risorse nidificate e le raccolte di livello root.Progettazione API REST: raccolta nidificata e nuova radice

Per dimostrare il concetto, supponiamo di disporre di raccolte City, Business e Employees. Un'API tipica può essere costruita come segue. Immagina che ABC, X7N e WWW siano chiavi, ad es. GUID:

GET Api/City/ABC/Businesses      (returns all Businesses in City ABC) 
GET Api/City/ABC/Businesses/X7N     (returns business X7N) 
GET Api/City/ABC/Businesses/X7N/Employees   (returns all employees at business X7N) 
PUT Api/City/ABC/Businesses/X7N/Employees/WWW  (updates employee WWW) 

Ciò appare pulita perché segue la struttura di dominio originale - business sono in una città, ei dipendenti sono a un business. I singoli articoli sono accessibili tramite chiave sotto la raccolta (ad esempio, ../Businesses restituisce tutte le attività commerciali, mentre ../Businesses/X7N restituisce l'attività individuale).

Ecco ciò che il consumatore API deve essere in grado di fare:

  • Get imprese in una città (GET Api/City/ABC/Businesses)
  • Ottenere tutti i dipendenti di un'azienda (GET Api/City/ABC/Businesses/X7N/Employees)
  • Aggiornare le informazioni di singolo dipendente (PUT Api/City/ABC/Businesses/X7N/Employees/WWW)

Quella seconda e terza chiamata, pur sembrando essere nel posto giusto, usano molti parametri che sono in realtà non validi Essary.

  • Per ottenere dipendenti in un'azienda, l'unico parametro necessario è la chiave dell'azienda (X7N).
  • Per aggiornare un singolo dipendente, l'unico parametro aveva bisogno la chiave del dipendente (WWW)

Nulla nel codice backend richiede informazioni non chiave per cercare il business o aggiornare il dipendente. Così, invece, i seguenti punti finali appaiono meglio:

GET Api/City/ABC/Businesses      (returns all Businesses in City ABC) 
GET Api/Businesses/X7N       (returns business X7N) 
GET Api/Businesses/X7N/Employees     (returns all employees at business X7N) 
PUT Api/Employees/WWW        (updates employee WWW) 

Come potete vedere, ho creato un nuovo radice per le imprese e dipendenti, anche se dal punto di vista del dominio sono un sub/sub-sub -collezione.

Nessuna soluzione mi sembra molto pulita.

  • Il primo esempio chiede informazioni non necessarie, ma è strutturato in modo che appare "naturale" al consumatore (singoli elementi da una raccolta sono recuperati mediante foglie inferiori)
  • Il secondo esempio richiede solo necessario informazioni, ma non è strutturato in modo "naturale" - le sottoraccolte sono accessibili tramite le radici
  • La singola radice dipendente non funzionerebbe quando si aggiunge un nuovo dipendente, poiché è necessario sapere a quale azienda aggiungere il dipendente, il che significa quella chiamata dovrebbe almeno risiedere sotto la radice aziendale, come ad esempio POST Api/Businesses/X7N7/Employees, il che rende tutto ancora più confuso.

C'è un detergente, una terza via a cui non sto pensando?

risposta

20

Io non vedo come REST aggiunge un vincolo che due risorse non potrebbero avere lo stesso valore. Il resourceType/ID è solo un esempio del caso d'uso più semplice piuttosto che il modo migliore per passare da un punto di vista RESTful.

Se leggete paragraph 5.2.1.1 della tesi di Roy Fielding con attenzione, si noterà che Fielding rende il disctinction tra un valore di e una risorsa. Ora una risorsa dovrebbe avere un URI univoco, è vero. Ma nulla impedisce che due risorse di avere lo stesso valore:

Per esempio, "versione preferita autori" le di un documento accademico è una mappatura cui i cambiamenti nel tempo il valore, mentre una mappatura per "la carta ha pubblicato nella gli atti della conferenza X "sono statici. Si tratta di due risorse distinte, anche se entrambe si associano allo stesso valore in un determinato momento. La distinzione è necessaria in modo che entrambe le risorse possano essere identificate e referenziate in modo indipendente. Un esempio simile di ingegneria del software è l'identificazione separata di un file di codice sorgente controllato dalla versione quando ci si riferisce alla "revisione più recente", alla "revisione numero 1.2.7" o alla "revisione inclusa con il rilascio Orange".

Quindi niente ti impedisce, come dici tu, di cambiare la radice. Nel tuo esempio, un Business è un valore non una risorsa. È perfettamente RESTful creare una risorsa che sia una lista di "ogni azienda situata in una città" (come nell'esempio di Roy, "revisioni incluse con la versione di Orange"), pur avendo una risorsa "business that ID is x" (come "numero di revisione x").

Per Employees, vorrei mantenere API/Businesses/X7N/Employees come la relazione tra un'azienda ei suoi dipendenti è un rapporto composition, e, quindi, come dici tu, Employees può e deve essere accessibile solo attraverso la radice Businesses di classe. Ma questo non è un requisito REST, e l'altra alternativa è perfettamente RESTful.


noti che questo va nella coppia con l'applicazione del principio HATEAOS. Nella tua API, l'elenco delle attività commerciali situate in una città potrebbe essere (e forse dovrebbe, da un punto di vista teorico) solo un elenco di collegamenti allo API/Businesses. Ma questo significherebbe che i clienti dovrebbero fare un viaggio di andata e ritorno verso il server per ognuno degli articoli nell'elenco. Questo non è efficiente e, per rimanere pragmatici, quello che faccio è incorporare la rappresentazione del business nella lista insieme al collegamento self all'URI che sarebbe in questo esempio API/Businesses.

2

La terza via che vedo è quello di rendere le imprese e le risorse di root Dipendenti e utilizzare parametri di query per filtrare collezioni:

GET Api/Businesses?city=ABC      (returns all Businesses in City ABC) 
GET Api/Businesses/X7N       (returns business X7N) 
GET Api/Employees?businesses=X7N     (returns all employees at business X7N) 
PUT Api/Employees/WWW        (updates employee WWW) 

I suoi due soluzioni utilizzano il concetto di riposo sotto-risorse che richiede che subresource è incluso nel risorsa genitore così:

GET Api/City/ABC/Businesses 

in risposta dovrebbe anche restituire i dati forniti da:

GET Api/City/ABC/Businesses/X7N     
    GET Api/City/ABC/Businesses/X7N/Employees 

simile per:

GET Api/Businesses/X7N 

che dovrebbe restituire i dati forniti da:

GET Api/Businesses/X7N/Employees 

Si farà dimensioni del tempo di risposta enorme e necessario per generare aumenterà.

Per rendere API REST pulita ogni risorsa dovrebbe avere un solo delimitata URI che maggese sotto modelli:

GET /resources 
GET /resources/{id} 
POST /resources 
PUT /resources/{id} 

Se avete bisogno di fare collegamenti tra le risorse utilizzano HATEOAS

1

Vai all'esempio 1. Non mi preoccuperei di informazioni non necessarie dal punto di vista del server. Un URL dovrebbe identificare chiaramente una risorsa in modo univoco dal punto di vista del cliente. Se il cliente non sapesse cosa significa /Employee/12 senza prima sapere che è effettivamente /Businesses/X7N/Employees/12 allora il primo URL sembra ridondante.

Il client deve occuparsi degli URL anziché dei singoli parametri che compongono gli URL, quindi non c'è niente di sbagliato con gli URL lunghi. Per il cliente sono solo stringhe. Il server dovrebbe comunicare al client l'URL per fare ciò che deve fare, non i singoli parametri che richiedono al client di costruire l'URL.

11

Non si deve confondere REST con l'applicazione di una convenzione di denominazione URI specifica.

COME le risorse sono denominate è interamente secondario. Stai tentando di utilizzare le convenzioni di denominazione delle risorse HTTP: questo non ha nulla a che fare con REST. Lo stesso Roy Fielding afferma così ripetutamente nei documenti citati sopra da altri. REST non è un protocollo, è uno stile architettonico.

Infatti, Roy Fielding stati nel suo blog 2008 commenti (http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven 2012/06/20):

"Un API REST non deve definire i nomi delle risorse fissi o gerarchie (un accoppiamento evidente di client e server) I server devono avere la libertà di controllare il proprio spazio dei nomi, invece, consentono ai server di istruire i clienti su come costruire URI appropriati, come avviene nei moduli HTML e nei modelli URI , definendo tali istruzioni all'interno dei tipi di media e delle relazioni di collegamento. "

Quindi, in sostanza:

Il problema che si descrive in realtà non è un problema di REST - concettualmente, si tratta di un problema di strutture gerarchiche contro strutture relazionali.

Mentre un'azienda è "in" una città e quindi può essere considerata parte della "gerarchia" della città, che dire delle società internazionali che hanno uffici in 75 città. Quindi la città diventa improvvisamente l'elemento junior in una gerarchia con il nome dell'azienda a livello senior della struttura.

Il punto è, è possibile visualizzare i dati da varie angolazioni e, a seconda del punto di vista, è più semplice vederlo come una gerarchia. Ma gli stessi dati possono essere visti come una gerarchia con diversi livelli. Quando si utilizzano nomi di risorse di tipo HTTP, è stata inserita una struttura gerarchica definita da HTTP. Questo è un vincolo, sì, ma non è un vincolo REST, è un vincolo HTTP.

Da tale angolo, è possibile scegliere la soluzione più adatta al proprio scenario. Se il cliente non può fornire il nome della città quando fornisce il nome della società (potrebbe non sapere), allora sarebbe meglio avere la chiave con il solo nome di città.Come ho detto, tocca a te, e REST non sta in piedi nel vostro senso ...

Più precisamente:

Gli unici vincoli vero riposo che hai, se avete già deciso di utilizzare HTTP con GET PUT e così via, sono:

    1. Tu non presumeth alcuna conoscenza preliminare ("fuori banda") tra client e server. *

Guardate il vostro proposta # 1 di cui sopra in quella luce. Presumi che i clienti conoscano le chiavi per le città che sono contenute nel tuo sistema? Sbagliato - non è riposante. Quindi il server deve dare l'elenco delle città come una lista di scelte in qualche modo. Quindi hai intenzione di elencare tutte le città del mondo qui? Credo di no, ma poi si dovrà fare un certo lavoro su come si sta progettando di fare questo, che ci porta a:

    1. A REST API dovrebbe spendere quasi tutto il suo sforzo descrittivo definire il tipo di supporto (s) utilizzato per rappresentare le risorse e guidare lo stato dell'applicazione ...

credo, leggendo il blog Roy Fielding menzionato ti aiuterà notevolmente.

3

In un progetto di URL RESTful-API non dovrebbe essere importante - o almeno un problema secondario poiché la rilevabilità è codificata nell'ipertesto e non nel percorso dell'URL. Dai un'occhiata alle risorse collegate nello REST tag wiki qui su StackOverflow.

Ma se si vuole progettare URL leggibili per il UC, vorrei suggerire quanto segue:

  1. utilizzare il tipo di risorsa che si sta creando/aggiornamento/interrogare come la prima parte dell'URL (dopo il prefisso API). Quindi, quando qualcuno vede l'URL, sa immediatamente a quali risorse questo URL punta. GET /Api/Employees... è l'unico modo per ricevere le risorse Employee dall'API.

  2. Utilizzare ID univoci per ciascuna risorsa indipendentemente dalle relazioni in cui si trovano in. Quindi GET /Api/<CollectionType>/UniqueKey dovrebbe restituire una rappresentazione di risorsa valida. Nessuno dovrebbe preoccuparsi di dove si trova il Dipendente. (Ma il Dipendente restituito dovrebbe avere i collegamenti con il Business (e per convenienza Città) a cui appartiene.) GET /Api/Employees/Z6W restituisce al Dipendente questo ID indipendentemente da dove si trova.

  3. Se si desidera ottenere una risorsa specifica: Inserire il parametro di query alla fine (invece nell'ordine gerarchico descritto nella domanda). È possibile utilizzare la stringa di query URL (GET /Api/Employees?City=X7N) o un'espressione di parametro matriciale (GET /Api/Employees;City=X7N;Business=A4X,A5Y). Ciò ti consentirà di esprimere facilmente una collezione di tutti i Dipendenti in una Città specifica, indipendentemente dal Business in cui si trovano.

nodo laterale:

Nella mia esperienza un modello di dati iniziale dominio gerarchica sopravvive di rado requisiti aggiuntivi che si presentano nel corso di un progetto. Nel tuo caso: considera un'attività situata in due città. È possibile creare una soluzione alternativa modellandola come due attività distinte, ma per quanto riguarda il dipendente che lavora metà tempo in un posto e l'altra metà nell'altro luogo? O ancora peggio: è chiaro solo per quali attività lavora, ma è indefinito, in quale città?