Concetti dell'API REST


10

Ho tre domande sul design dell'API REST che spero che qualcuno possa far luce. Ho cercato incessantemente per molte ore ma non ho trovato risposte alle mie domande da nessuna parte (forse non so proprio cosa cercare?).

Domanda 1

La mia prima domanda riguarda le azioni / RPC. Sto sviluppando un'API REST da un po 'e sono abituato a pensare alle cose in termini di raccolte e risorse. Tuttavia, mi sono imbattuto in un paio di casi in cui il paradigma non sembra applicarsi e mi chiedo se c'è un modo per conciliare questo con il paradigma REST.

In particolare, ho un caso in cui la modifica di una risorsa provoca la generazione di un'e-mail. Tuttavia, in un secondo momento l'utente può indicare in modo specifico che desidera inviare nuovamente l'e-mail inviata in precedenza. Quando si reinvia l'e-mail, nessuna risorsa viene modificata. Nessuno stato è cambiato. È semplicemente un'azione che deve avvenire. L'azione è legata al tipo di risorsa specifica.

È appropriato combinare una sorta di call action con un URI di risorsa (ad es. /collection/123?action=resendEmail)? Sarebbe meglio specificare l'azione e passargli l'ID risorsa (es. /collection/resendEmail?id=123)? È questo il modo sbagliato di farlo? Tradizionalmente (almeno con HTTP) l'azione eseguita è il metodo di richiesta (GET, POST, PUT, DELETE), ma quelli non consentono realmente azioni personalizzate con una risorsa.

Domanda 2

Uso la porzione di querystring dell'URL per filtrare l'insieme di risorse restituite durante l'interrogazione di una raccolta (ad es /collection?someField=someval.). All'interno del mio controller API, quindi, determino quale tipo di confronto ha a che fare con quel campo e valore. Ho scoperto che questo non funziona davvero. Ho bisogno di un modo per consentire all'utente dell'API di specificare il tipo di confronto che desidera eseguire.

L'idea migliore che mi è venuta finora è di consentire all'utente dell'API di specificarlo come appendice al nome del campo (ad es /collection?someField:gte=someval. Per indicare che dovrebbe restituire risorse dove someFieldè maggiore o uguale a (> =) qualunque cosa somevalsia È una buona idea? Una cattiva idea? In caso affermativo, perché? Esiste un modo migliore per consentire all'utente di specificare il tipo di confronto da eseguire con il campo e il valore indicati?

Domanda 3

Vedo spesso di URI che aspetto qualcosa di simile /person/123/dogsper ottenere i persons dogs. In genere ho evitato qualcosa del genere perché alla fine immagino che creando un URI come quello in realtà stai solo accedendo a una dogsraccolta filtrata da un personID specifico . Sarebbe equivalente a /dogs?person=123. C'è mai davvero una buona ragione per cui un URI REST sia profondo più di due livelli ( /collection/resource_id)?


10
Hai tre domande. Perché non pubblicarli separatamente?
anaximander,

3
Sarebbe meglio suddividerlo in 3 domande separate. Uno spettatore può essere in grado di rispondere in modo eccellente a una, ma non a tutte le domande.

2
Penso che siano tutti correlati. Il titolo è un po 'di alto livello ma questa domanda aiuterà molte persone ed è facilmente reperibile durante una ricerca SE. Questa domanda dovrebbe diventare Wiki della community una volta aggiunti abbastanza voti e sostanza. Mi ci sono volute settimane per cercare queste cose.
Andrew T Finnell,

1
Potrebbe essere stato meglio pubblicarli separatamente, IDK. Tuttavia, come menzionato da @AndrewFinnell, ho pensato che sarebbe stata una buona idea tenere insieme le domande poiché queste sono state le domande più difficili relative al REST che ho avuto e sarebbe bello che altre persone fossero in grado di trovare le risposte insieme.
Justin Warkentin,

Risposte:


11

È appropriato combinare una sorta di call action con un URI di risorsa (ad es. /collection/123?action=resendEmail)? Sarebbe meglio specificare l'azione e passargli l'ID risorsa (es. /collection/resendEmail?id=123)? È questo il modo sbagliato di farlo? Tradizionalmente (almeno con HTTP) l'azione eseguita è il metodo di richiesta (GET, POST, PUT, DELETE), ma quelli non consentono realmente azioni personalizzate con una risorsa.

Preferirei modellarlo in un modo diverso, con una raccolta di risorse che rappresentano le e-mail che devono essere inviate; l'invio verrà elaborato dagli interni del servizio a tempo debito, a quel punto la risorsa corrispondente verrà rimossa. (O l'utente potrebbe ELIMINARE la risorsa in anticipo, causando l'annullamento della richiesta per l'invio.)

Qualunque cosa tu faccia, non mettere i verbi nel nome della risorsa! Questo è il sostantivo (e la parte della query è l'insieme di aggettivi). I verbi dei nomi strani REST!

Uso la porzione di querystring dell'URL per filtrare l'insieme di risorse restituite durante l'interrogazione di una raccolta (ad es /collection?someField=someval.). All'interno del mio controller API, quindi, determino quale tipo di confronto ha a che fare con quel campo e valore. Ho scoperto che questo non funziona davvero. Ho bisogno di un modo per consentire all'utente dell'API di specificare il tipo di confronto che desidera eseguire.

L'idea migliore che mi è venuta finora è di consentire all'utente dell'API di specificarlo come appendice al nome del campo (ad es /collection?someField:gte=someval. Per indicare che dovrebbe restituire risorse dove someField è maggiore o uguale a ( >=) qualunque cosa somevalsia. È una buona idea? Una cattiva idea? In caso affermativo, perché? Esiste un modo migliore per consentire all'utente di specificare il tipo di confronto da eseguire con il campo e il valore indicati?

Preferirei specificare una clausola di filtro generale e includerla come parametro di query facoltativo su qualsiasi richiesta per recuperare il contenuto della raccolta. Il cliente può quindi specificare esattamente come limitare l'insieme restituito, nel modo desiderato. Mi preoccuperei anche un po 'della rilevabilità del linguaggio filtro / query; più ricco lo rendi, più difficile è scoprire i clienti arbitrari. Un approccio alternativo che, almeno teoricamente, affronta quel problema di rilevabilità è quello di consentire di creare risorse secondarie di restrizione della raccolta, che i clienti ottengono pubblicando un documento che descrive la restrizione alla risorsa di raccolta. È ancora un leggero abuso, ma almeno è uno che puoi chiaramente scoprire!

Questo tipo di rilevabilità è una delle cose che trovo meno forte con REST.

Vedo spesso degli URI che assomigliano /person/123/dogsa quelli dei cani. In genere ho evitato qualcosa del genere perché alla fine immagino che creando un URI come quello in realtà stai solo accedendo a una collezione di cani filtrata da un ID persona specifico. Sarebbe equivalente a /dogs?person=123. C'è mai davvero una buona ragione per cui un URI REST sia profondo più di due livelli ( /collection/resource_id)?

Quando la raccolta nidificata è veramente una sotto-caratteristica delle entità membro della raccolta esterna, è ragionevole strutturarle come sotto-risorsa. Per "caratteristica secondaria" intendo qualcosa come la relazione di composizione UML, dove distruggere la risorsa esterna significa naturalmente distruggere la raccolta interna.

Altri tipi di raccolta possono essere modellati come reindirizzamento HTTP; quindi si /person/123/dogspuò effettivamente rispondere facendo un 307 che reindirizza a /dogs?person=123. In questo caso, la raccolta non è in realtà una composizione UML, ma piuttosto un'aggregazione UML. La differenza conta; è significativo!


2
Hai punti solidi nel complesso. Tuttavia, mentre l' resendEmailazione potrebbe essere gestita creando una raccolta e POSTANDO su di essa, ciò sembra meno naturale. In effetti, non memorizzo nulla nel database quando viene rinviata un'e-mail (non è necessario). Nessuna risorsa viene modificata, quindi è semplicemente un'azione che ha successo o fallisce. Non ho potuto restituire un ID risorsa esistente oltre la durata della chiamata, rendendo tale implementazione un hack invece di essere RESTful. Semplicemente non è un'operazione CRUD.
Justin Warkentin,

3

È comprensibile essere un po 'confuso su come utilizzare correttamente REST in base a tutti i modi in cui ho visto grandi aziende progettare le loro API REST.

Hai ragione in quanto REST è un sistema di raccolta delle risorse. È l'acronimo di Representational State Transfer. Non un'ottima definizione se me lo chiedi. Ma i concetti principali sono i 4 VERB HTTP e l'apolidia.

Il pezzo importante da notare è che hai solo 4 VERBI con REST. Questi sono GET, POST, PUT e DELETE. Il tuo resendesempio sarebbe l'aggiunta di un nuovo verbo a REST. Questa dovrebbe essere una bandiera rossa.

Domanda 1

È importante rendersi conto che il chiamante dell'API REST non dovrebbe sapere che l'esecuzione di un PUTnella raccolta comporterebbe la generazione di un'e-mail. Questo mi profuma di una perdita. Quello che potrebbero sapere è che l'esecuzione di a PUTpotrebbe comportare attività extra che potrebbero essere interrogate in seguito. Lo saprebbero eseguendo una GETrisorsa recentemente creata. Ciò GETrestituirebbe la risorsa e tutti gli TaskID risorsa associati. Successivamente, è possibile eseguire una query su tali attività per determinarne lo stato e persino inviarne una nuova Task.

Hai alcune opzioni.

REST - Approccio basato sulle risorse dell'attività

Creare una tasksrisorsa in cui è possibile inviare attività specifiche nel proprio sistema per eseguire azioni. È quindi possibile GETl'attività in base alla IDrestituita per determinarne lo stato.

Oppure puoi combinare un SOAP over HTTPservizio Web per aggiungere un po 'di RPC alla tua architettura.

interrogazione per tutte le attività per una risorsa specifica

GET http://server/api/myCollection/123/tasks

{ "tasks" :
    [ { "22333" : "http://server/api/tasks/223333" } ] 
}

esempio di risorsa attività

PUT http://server/api/tasks

{ 
    "type" : "send-email" , 
    "parameters" : 
    { 
         "collection-type" : "foo" , 
         "collection-id" : "123" 
    } 
}

==> restituisce l'id dell'attività

223334

GET http://server/api/tasks/223334

{ 
    "status" : "complete" , 
    "date" : "whenever" 
}

RIPOSO - Utilizzo di POST per attivare azioni

Puoi sempre POSTaggiungere dati a una risorsa. Secondo me ciò violerebbe lo spirito di REST ma sarebbe comunque conforme.

Puoi fare un POST simile a questo:

POST http://server/api/collection/123

{ "action" : "send-email" }

Aggiornerai la risorsa 123 dalla raccolta con dati aggiuntivi. Tali dati aggiuntivi sono essenzialmente un'azione che dice al back-end di inviare un'e-mail per quella risorsa.

Il problema che ho con questo è che un GETsulla risorsa restituirà questi dati aggiornati. Tuttavia, ciò risolverebbe i tuoi requisiti e rimarrebbe comunque RESTful.

SOAP - Servizio Web che accetta risorse ottenute da REST

Creare un nuovo servizio Web in cui è possibile inviare e-mail in base all'ID risorsa precedente dall'API REST. Non entrerò nei dettagli di SOAP qui poiché la domanda originale riguarda REST e questi due concetti / tecnologie non dovrebbero essere confrontati in quanto sono mele e arance .

Domanda 2

Hai anche alcune opzioni qui:

Sembra che molte grandi aziende che pubblicano le API REST espongano una searchraccolta che è davvero solo un modo per passare parametri di query per restituire risorse.

GET http://server/api/search?q="type = myCollection & someField >= someval"

Che restituirebbe una raccolta di risorse REST pienamente qualificate come:

{
    "results" : 
       { [ 
             "location" : "http://server/api/myCollection/1",
             "location" : "http://server/api/myCollection/9",
             "location" : "http://server/api/myCollection/56"
         ]
       }
}

Oppure puoi consentire qualcosa come MVEL come parametro di query.

Domanda 3

Preferisco i sotto-livelli piuttosto che dover tornare indietro e interrogare l'altra risorsa con un parametro query. Non credo che ci sia alcuna regola in un modo o nell'altro. È possibile implementare entrambi i modi e consentire al chiamante di decidere quale sia più appropriato in base al modo in cui è entrato per la prima volta nel sistema.

Appunti

Non sono d'accordo sui commenti di leggibilità degli altri. Nonostante ciò che alcuni potrebbero pensare, REST non è ancora destinato al consumo umano. È per il consumo della macchina. Se voglio vedere i miei tweet, utilizzo il normale sito Web di Twitter. Non eseguo un REST GET con la loro API. Se voglio fare qualcosa a livello di programmazione con i miei tweet, allora uso la loro API REST. Sì, le API dovrebbero essere comprensibili, ma la tua gtenon è poi così male, non è semplicemente intuitiva.

L'altra cosa principale con REST è che dovresti essere in grado di iniziare in qualsiasi punto della tua API e navigare verso tutte le altre risorse associate SENZA conoscere l'URL esatto delle altre risorse in anticipo. I risultati del GETVERB in REST dovrebbero restituire l'URL REST completo delle risorse a cui fa riferimento. Quindi, invece di una query che restituisce l'ID di un Personoggetto, restituirebbe l'URL completo come http://server/api/people/13. Quindi puoi sempre navigare programmaticamente i risultati anche se l'URL è cambiato.

Risposta al commento

Nel mondo reale ci sono in effetti cose che devono accadere che non creano, leggono, aggiornano o eliminano (CRUD) una risorsa.

Ulteriori azioni possono essere intraprese sulle risorse. Tipici database relazionali supportano il concetto di Stored Procedures. Questi sono comandi aggiuntivi che possono essere eseguiti su un set di dati. REST non ha intrinsecamente questo concetto. E non c'è motivo per cui dovrebbe. Questi tipi di azioni sono perfetti per i servizi Web RPC o SOAP.

Questo è il problema generale che vedo con le API REST. Agli sviluppatori non piacciono i limiti concettuali che circondano il REST, quindi lo adattano per fare quello che vorrebbero. Ciò lo rompe dall'essere un servizio RESTful però. In sostanza, tali URL diventano GETchiamate su servlet pseudo-REST-like.

Hai alcune opzioni:

  • Creare una risorsa di attività
  • Supporto di POSTdati aggiuntivi per la risorsa per eseguire un'azione.
  • Aggiungere i comandi aggiuntivi tramite un servizio Web SOAP.

Se utilizzassi un parametro di query quale VERB HTTP useresti per reinviare l'e-mail?

  • GET- Questo reinvia l'e-mail E restituisce i dati della risorsa? E se un sistema memorizzasse nella cache quell'URL e lo trattasse come l'URL univoco per quella risorsa. Ogni volta che colpiscono l'URL, invia di nuovo un'email.
  • POST - In realtà non hai inviato nuovi dati alla risorsa, solo un parametro di query aggiuntivo.

Sulla base di tutti i requisiti indicati, fare un problema POSTcon la risorsa con un action fielddato POST risolverà il problema.


3
Mentre REST implementato tramite HTTP ti dà quei 4 verbi, non sono convinto che quei verbi dovrebbero essere la fine di esso. Nel mondo reale ci sono in effetti cose che devono accadere che non creano, leggono, aggiornano o eliminano (CRUD) una risorsa. Reinviare un'e-mail è una di queste cose. Non ho bisogno di archiviare o modificare nulla nel database. È semplicemente un'azione che ha successo o fallisce.
Justin Warkentin,

@JustinWarkentin Capisco quali sono le tue esigenze. Ma ciò non rende REST qualcosa che non lo è. L'aggiunta di un nuovo verbo all'URL è contro l'architettura REST. Aggiornerò la mia risposta per offrire un'altra alternativa che sarebbe RESTful.
Andrew T Finnell

@JustinWarkentin Dai un'occhiata a "REST - Utilizzo di POST per attivare azioni" nella mia risposta.
Andrew T Finnell,

0

Domanda 1: È opportuno combinare una sorta di chiamata di azione con un URI di risorsa [o] sarebbe meglio specificare l'azione e passargli l'ID risorsa?

Buona domanda. In questo caso, ti consiglio di utilizzare quest'ultimo approccio, ovvero specificare l'azione e passargli un ID risorsa. In questo modo, quando la tua risorsa viene modificata per la prima volta, a sua volta chiama l' /sendEmailazione (nota a margine: non è necessario chiamarla "reinvia") come una richiesta RESTful separata (che puoi successivamente chiamare ancora e ancora, indipendentemente dalla risorsa modificata ).

Domanda 2: riguardo all'uso di un operatore di confronto in questo modo:/collection?someField:gte=someval

Anche se tecnicamente va bene, è probabilmente una cattiva idea. Uno dei principi chiave di REST è la leggibilità. Ti suggerirei semplicemente di passare l'operatore di confronto come un altro parametro, ad esempio: /collection?someField=someval&operator=gtee ovviamente progettare la tua API in modo che approvi un caso predefinito (nel caso in cui il operatorparametro venga lasciato fuori dall'URI).

Domanda 3: c'è mai davvero una buona ragione per cui un URI REST sia profondo più di due livelli?

Sì; per astrazione. Ho visto un paio di API REST che utilizzano livelli di astrazione attraverso più livelli URI, ad esempio: /vehicles/cars/123o /vehicles/bikes/123che a loro volta ti consente di lavorare con informazioni utili relative a entrambi /vehiclese alle /vehicles/bikesraccolte. Detto questo, non sono un grande fan di questo approccio; raramente dovrai farlo in pratica e è probabile che tu possa riprogettare l'API per usare solo 2 livelli.

E sì, come suggeriscono i commenti sopra, in futuro sarebbe meglio dividere le tue domande in post separati;)


Penso che il mio esempio per la domanda n. 2 fosse troppo semplicistico. Devo specificare un operatore di confronto per ciascun campo utilizzato per filtrare la raccolta, non solo uno, quindi nel tuo esempio dovrebbe essere qualcosa di simile /collection?field1=someval&field1Operator=gte&field2=someval&field2Operator=eq.
Justin Warkentin,

0

Per la domanda 2, un'alternativa diversa può essere più flessibile: considera ogni ricerca una risorsa che l'utente crea prima dell'uso.

supponiamo che tu abbia un contenitore "ricerche", lì fai un POST /api/searches/con la specifica della query sul contenuto. potrebbe essere un documento JSON, XML o addirittura SQL, qualunque cosa sia più facile per te. Se la query viene analizzata correttamente, viene creata una nuova ricerca come nuova risorsa con il proprio URI/api/searches/q123/

Quindi, il client può semplicemente GET /api/searches/q123/recuperare i risultati della query.

Infine, puoi chiedere al client di eliminare la query o eliminarla dopo aver chiuso la sessione.


0

È appropriato combinare una sorta di chiamata di azione con un URI di risorsa (ad esempio / collection / 123? Action = resendEmail)? Sarebbe meglio specificare l'azione e passargli l'ID risorsa (es. / Collection / resendEmail? Id = 123)? È questo il modo sbagliato di farlo? Tradizionalmente (almeno con HTTP) l'azione eseguita è il metodo di richiesta (GET, POST, PUT, DELETE), ma quelli non consentono realmente azioni personalizzate con una risorsa.

No, non è appropriato, dal momento che gli IRI servono per identificare le risorse e non le operazioni (tuttavia ppl usa questo metodo per ignorare l'approccio per un po ', nei casi in cui l'uso di metodi non POST e GET non è supportato). Quello che puoi fare è cercare un metodo HTTP appropriato o crearne uno nuovo. POST può essere tuo amico in questi casi (utilizzalo se non riescono a trovare un metodo appropriato e la richiesta non viene recuperata). Un altro approccio per rendere le risorse dall'invio di e-mail e quindi POST /emailspuò inviare le e-mail senza creare una vera risorsa. Btw. La struttura dell'URI non porta la semantica, quindi dal punto di vista REST non importa davvero quale tipo di URI usi. Ciò che conta sono i metadati (ad es. Relazione di collegamento ) assegnati ai collegamenti inviati ai clienti.

L'idea migliore che mi è venuta finora è di consentire all'utente API di specificarlo come appendice al nome del campo (ad es. / Collection? SomeField: gte = someval - per indicare che dovrebbe restituire risorse dove someField è maggiore di o uguale a (> =) qualunque sia il someval. È una buona idea? Una cattiva idea? In caso affermativo, perché? Esiste un modo migliore per consentire all'utente di specificare il tipo di confronto da eseguire con il campo e il valore indicati?

Non è necessario creare un proprio linguaggio di query. Preferirei usarne uno già esistente e aggiungere una descrizione della query ai metadati del collegamento. Probabilmente dovresti usare un tipo di supporto RDF (es. JSON-LD) per farlo o usare un tipo MIME personalizzato (non esiste un formato non RDF che supporti questo). L'uso degli standard esistenti disaccoppia il client dal server, questo è il vincolo uniforme dell'interfaccia.

Sarebbe equivalente a / dogs? Person = 123. C'è mai davvero una buona ragione per cui un URI REST sia profondo più di due livelli (/ collection / resource_id)?

Come accennato in precedenza, la struttura dell'URI non ha importanza dal punto di vista REST. Potresti usare /x71fd823df2per esempio. Avrebbe comunque senso per i clienti perché controllano i metadati assegnati ai collegamenti e non la struttura dell'URI. Lo scopo principale dell'URI è l'identificazione delle risorse. Nello standard URI affermano che il percorso contiene dati gerarchici e la query contiene dati non gerarchici. Ma può essere molto soggettivo ciò che è gerarchico. Ecco perché soddisfi URI e URI profondi a più livelli con lunghe query.

Ho cercato incessantemente per molte ore ma non ho trovato risposte alle mie domande da nessuna parte (forse non so proprio cosa cercare?).

Dovresti leggere almeno i vincoli REST della tesi di Fielding , dello standard HTTP e probabilmente delle API Web di terza generazione di Markus.

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.