API REST – Crea bulk o Aggiorna in un’unica richiesta

Supponiamo che ci siano due risorse Binder e Doc con relazione di associazione che indicano che Doc e Binder sono indipendenti. Doc potrebbe o potrebbe non appartenere a Binder e Binder potrebbe essere vuoto.

Se voglio progettare un’API REST che consenta all’utente di inviare una raccolta di Doc , IN UNA SINGOLA RICHIESTA , come la seguente:

 { "docs": [ {"doc_number": 1, "binder": 1}, {"doc_number": 5, "binder": 8}, {"doc_number": 6, "binder": 3} ] } 

E per ogni documento nei docs ,

  • Se il doc esiste, assegnarlo a Binder
  • Se il doc non esiste, crearlo e quindi assegnarlo

Sono davvero confuso su come dovrebbe essere implementato.

  • Quale metodo HTTP utilizzare?
  • Quale codice di risposta deve essere restituito?
  • Questo è qualificato per REST?
  • Come sarebbe l’URI? /binders/docs ?
  • Gestire la richiesta in blocco, cosa succede se alcuni elementi sollevano un errore ma l’altro lo attraversa. Quale codice di risposta deve essere restituito? La massa dovrebbe essere atomica?

    Penso che potresti usare un metodo POST o PATCH per gestire questo dato che di solito progettano per questo.

    • L’utilizzo di un metodo POST viene in genere utilizzato per aggiungere un elemento quando utilizzato nella risorsa elenco ma è anche ansible supportare diverse azioni per questo metodo. Vedere questa risposta: Come aggiornare una raccolta di risorse REST . È anche ansible supportare diversi formati di rappresentazione per l’input (se corrispondono a un array oa un singolo elemento).

      Nel caso, non è necessario definire il formato per descrivere l’aggiornamento.

    • Anche l’utilizzo di un metodo PATCH è adatto poiché le richieste corrispondenti corrispondono ad un aggiornamento parziale. Secondo RFC5789 ( http://tools.ietf.org/html/rfc5789 ):

      Diverse applicazioni che estendono il protocollo HTTP (Hypertext Transfer Protocol) richiedono che una funzione esegua una modifica parziale delle risorse. Il metodo HTTP PUT esistente consente solo la sostituzione completa di un documento. Questa proposta aggiunge un nuovo metodo HTTP PATCH per modificare una risorsa HTTP esistente.

      Nel caso, devi definire il tuo formato per descrivere l’aggiornamento parziale.

    Penso che in questo caso, POST e PATCH siano abbastanza simili dal momento che non è necessario descrivere l’operazione da eseguire per ciascun elemento. Direi che dipende dal formato della rappresentazione da inviare.

    Il caso di PUT è un po ‘meno chiaro. In effetti, quando si utilizza un metodo PUT , è necessario fornire l’intera lista. Di fatto, la rappresentazione fornita nella richiesta sarà in sostituzione della risorsa lista.

    È ansible avere due opzioni relative ai percorsi delle risorse.

    • Utilizzo del percorso della risorsa per l’elenco dei documenti

    In questo caso, è necessario fornire esplicitamente il link dei documenti con un raccoglitore nella rappresentazione fornita nella richiesta.

    Ecco un esempio di percorso per questo /docs .

    Il contenuto di tale approccio potrebbe essere per il metodo POST :

     [ { "doc_number": 1, "binder": 4, (other fields in the case of creation) }, { "doc_number": 2, "binder": 4, (other fields in the case of creation) }, { "doc_number": 3, "binder": 5, (other fields in the case of creation) }, (...) ] 
    • Utilizzo del percorso di risorse secondarie dell’elemento legante

    Inoltre, è ansible prendere in considerazione l’utilizzo di percorsi secondari per descrivere il collegamento tra documenti e raccoglitori. I suggerimenti relativi all’associazione tra un doc e un raccoglitore non devono ora essere specificati nel contenuto della richiesta.

    Ecco un percorso di esempio per questo /binder/{binderId}/docs . In questo caso, l’invio di un elenco di documenti con un metodo POST o PATCH allegherà documenti al raccoglitore con identificatore binderId dopo aver creato il documento se non esiste.

    Il contenuto di tale approccio potrebbe essere per il metodo POST :

     [ { "doc_number": 1, (other fields in the case of creation) }, { "doc_number": 2, (other fields in the case of creation) }, { "doc_number": 3, (other fields in the case of creation) }, (...) ] 

    Per quanto riguarda la risposta, sta a te definire il livello di risposta e gli errori da restituire. Vedo due livelli: il livello di stato (livello globale) e il livello di carico utile (livello più sottile). Spetta anche a te definire se tutti gli inserti / aggiornamenti corrispondenti alla tua richiesta devono essere atomici o meno.

    • Atomico

    In questo caso, puoi sfruttare lo stato HTTP. Se tutto va bene, ottieni uno status 200 . In caso contrario, un altro stato come 400 se i dati forniti non sono corretti (ad esempio l’id del raccoglitore non è valido) o qualcos’altro.

    • Non atomico

    In questo caso, verrà restituito uno stato 200 sarà la rappresentazione della risposta a descrivere cosa è stato fatto e dove si verificano gli errori. ElasticSearch ha un endpoint nella sua API REST per l’aggiornamento collettivo. Questo potrebbe darti alcune idee a questo livello: http://www.elasticsearch.org/guide/en/elasticsearch/guide/current/bulk.html .

    • asincrono

    È inoltre ansible implementare un’elaborazione asincrona per gestire i dati forniti. In questo caso, lo stato HTTP restituito sarà 202 . Il cliente deve estrarre una risorsa aggiuntiva per vedere cosa succede.

    Prima di finire, vorrei anche notare che la specifica OData affronta il problema relativo alle relazioni tra entity framework con la funzionalità denominata collegamenti di navigazione . Forse potresti dare un’occhiata a questo 😉

    Il seguente link può anche aiutarti: https://templth.wordpress.com/2014/12/15/designing-a-web-api/ .

    Spero che ti aiuti, Thierry

    Probabilmente sarà necessario utilizzare POST o PATCH, perché è improbabile che una singola richiesta che aggiorna e crei più risorse sarà idempotente.

    Fare PATCH /docs è sicuramente un’opzione valida. Potresti trovare l’utilizzo dei formati di patch standard difficili per il tuo scenario particolare. Non sono sicuro di questo.

    Potresti usare 200. Potresti anche usare 207 – Multi Status

    Questo può essere fatto in modo RESTful. La chiave, a mio avviso, è avere una risorsa progettata per accettare un insieme di documenti da aggiornare / creare.

    Se usi il metodo PATCH, penserei che la tua operazione debba essere atomica. cioè non userei il codice di stato 207 e quindi riferisco successi e fallimenti nel corpo della risposta. Se si utilizza l’operazione POST, allora l’approccio 207 è fattibile. Dovrai progettare il tuo corpo di risposta per comunicare quali operazioni hanno avuto successo e quali sono fallite. Non sono a conoscenza di uno standardizzato.

    PUT ing

    PUT /binders/{id}/docs Crea o aggiorna e mette in relazione un singolo documento con un raccoglitore

    per esempio:

     PUT /binders/1/docs HTTP/1.1 { "docNumber" : 1 } 

    PATCH ing

    PATCH /docs Crea documenti se non esistono e li mettono in relazione con i raccoglitori

    per esempio:

     PATCH /docs HTTP/1.1 [ { "op" : "add", "path" : "/binder/1/docs", "value" : { "doc_number" : 1 } }, { "op" : "add", "path" : "/binder/8/docs", "value" : { "doc_number" : 8 } }, { "op" : "add", "path" : "/binder/3/docs", "value" : { "doc_number" : 6 } } ] 

    Includerò ulteriori approfondimenti in seguito, ma nel frattempo, se lo desideri, dai un’occhiata a RFC 5789 , RFC 6902 e William Durand’s Please. Non applicare patch come un post di blog idiota .

    In un progetto a cui ho lavorato, abbiamo risolto questo problema implementando qualcosa che abbiamo chiamato richieste “Batch”. Abbiamo definito un percorso /batch cui abbiamo accettato json nel seguente formato:

     [ { path: '/docs', method: 'post', body: { doc_number: 1, binder: 1 } }, { path: '/docs', method: 'post', body: { doc_number: 5, binder: 8 } }, { path: '/docs', method: 'post', body: { doc_number: 6, binder: 3 } }, ] 

    La risposta ha il codice di stato 207 (Multi-Status) e assomiglia a questo:

     [ { path: '/docs', method: 'post', body: { doc_number: 1, binder: 1 } status: 200 }, { path: '/docs', method: 'post', body: { error: { msg: 'A document with doc_number 5 already exists' ... } }, status: 409 }, { path: '/docs', method: 'post', body: { doc_number: 6, binder: 3 }, status: 200 }, ] 

    Puoi anche aggiungere il supporto per le intestazioni in questa struttura. Abbiamo implementato qualcosa che si è rivelato utile quali variabili da utilizzare tra le richieste in un batch, il che significa che possiamo utilizzare la risposta da una richiesta come input a un’altra.

    Facebook e Google hanno implementazioni simili:
    https://developers.google.com/gmail/api/guides/batch
    https://developers.facebook.com/docs/graph-api/making-multiple-requests

    Quando si desidera creare o aggiornare una risorsa con la stessa chiamata, utilizzerei POST o PUT a seconda del caso. Se il documento esiste già, vuoi che l’intero documento sia:

    1. Sostituito dal documento inviato (ovvero le proprietà mancanti nella richiesta saranno rimosse e già esistenti sovrascritte)?
    2. Unita con il documento inviato (ovvero le proprietà mancanti nella richiesta non verranno rimosse e le proprietà già esistenti verranno sovrascritte)?

    Nel caso in cui si desideri il comportamento da 1 alternativo, è necessario utilizzare un POST e nel caso in cui si desideri il comportamento da alternativo 2 è necessario utilizzare PUT.

    http://restcookbook.com/HTTP%20Methods/put-vs-post/

    Come già suggerito dalla gente, potresti anche provare PATCH, ma preferisco mantenere le API semplici e non usare verbi extra se non sono necessari.