Come dovrebbe un'API REST gestire le richieste PUT a risorse parzialmente modificabili?

41

Supponiamo che un'API REST, in risposta a una richiesta GET HTTP, restituisca alcuni dati aggiuntivi in un oggetto secondario owner :

{
  id: 'xyz',
  ... some other data ...
  owner: {
    name: 'Jo Bloggs',
    role: 'Programmer'
  }
}

Chiaramente, non vogliamo che nessuno possa tornare a PUT indietro

{
  id: 'xyz',
  ... some other data ...
  owner: {
    name: 'Jo Bloggs',
    role: 'CEO'
  }
}

e ce l'hai fatta. In effetti, probabilmente non stiamo nemmeno implementare un modo per farlo, anche in questo caso, in questo caso.

Ma questa domanda non riguarda solo i sottooggetti: cosa, in generale, dovrebbe essere fatto con i dati che non dovrebbero essere modificabili in una richiesta PUT?

Dovrebbe essere mancante dalla richiesta PUT?

Dovrebbe essere scartato in silenzio?

Dovrebbe essere selezionato e se differisce dal vecchio valore di quell'attributo, restituire un codice di errore HTTP nella risposta?

O dovremmo usare le patch JSON RFC 6902 invece di inviare l'intero JSON?

    
posta Robin Green 14.08.2013 - 17:19
fonte

2 risposte

38

Non esiste una regola, né nelle specifiche W3C né nelle regole non ufficiali di REST, che afferma che PUT deve utilizzare lo stesso schema / modello del corrispondente GET .

È bello che siano simili , ma non è insolito che PUT faccia le cose in modo leggermente diverso. Ad esempio, ho visto un sacco di API che includono una sorta di ID nel contenuto restituito da un GET , per comodità. Ma con un PUT , quell'ID è determinato esclusivamente dall'URI e non ha significato nel contenuto. Qualsiasi ID trovato nel corpo verrà ignorato silenziosamente.

REST e il web in generale sono strongmente legati al Principio di robustezza : "Sii prudente in ciò che fai [invia], sii liberale in ciò che accetti. " Se sei d'accordo filosoficamente con questo, allora la soluzione è ovvia: ignora qualsiasi dato non valido in PUT richieste. Questo vale sia per i dati immutabili, come nel tuo esempio, sia per quelli privi di senso, ad es. campi sconosciuti.

PATCH è potenzialmente un'altra opzione, ma non dovresti implementare PATCH a meno che non supporti effettivamente gli aggiornamenti parziali. PATCH significa che aggiorna solo gli attributi specifici che includo nel contenuto ; non significa sostituire l'intera entità ma escludere alcuni campi specifici . Quello di cui stai parlando in realtà non è un aggiornamento parziale, è un aggiornamento completo, idempotente e tutto, è solo che parte della risorsa è di sola lettura.

Una cosa carina da fare se si sceglie questa opzione è quella di rimandare un 200 (OK) con l'entità effettiva aggiornata nella risposta, in modo che i client possano vedere chiaramente che la sola lettura i campi non sono stati aggiornati.

Ci sono sicuramente alcune persone che pensano nell'altro senso - che dovrebbe essere un errore tentare di aggiornare una lettura -solo porzione di una risorsa. C'è qualche giustificazione per questo, principalmente sulla base del fatto che si restituirebbe sicuramente un errore se la risorsa intera era di sola lettura e l'utente ha provato ad aggiornarlo. Sicuramente va contro il principio di robustezza, ma potresti considerarlo più "auto-documentante" per gli utenti della tua API.

Ci sono due convenzioni per questo, che corrispondono entrambe alle tue idee originali, ma mi dilungherò su di esse. Il primo consiste nel proibire che i campi di sola lettura vengano visualizzati nel contenuto e restituire un HTTP 400 (Richiesta non valida) se lo fa. Le API di questo tipo dovrebbero anche restituire un HTTP 400 se ci sono altri campi non riconosciuti / inutilizzabili. Il secondo è richiedere che i campi di sola lettura siano identici al contenuto corrente e restituire un 409 (Conflitto) se i valori non corrispondono.

Non mi piace davvero il controllo di uguaglianza con 409 perché invariabilmente richiede al client di eseguire un GET per recuperare i dati correnti prima di poter eseguire un PUT . Non è carino e probabilmente porterà a risultati mediocri, per qualcuno, da qualche parte. Anche really non mi piace per questo 403 (Forbidden) perché implica che la intera risorsa è protetta, non solo una parte di essa. Quindi la mia opinione è, se devi assolutamente convalidare invece di seguire il principio di robustezza, convalidare tutte delle tue richieste e restituire un 400 per qualsiasi che abbia campi extra o non scrivibili.

Assicurati che il tuo 400/409 / qualsiasi cosa includa informazioni su quale sia il problema specifico e su come risolverlo.

Entrambi questi approcci sono validi, ma preferisco il precedente in linea con il principio di robustezza. Se hai mai provato a lavorare con con una grande API REST, apprezzerai il valore della compatibilità con le versioni precedenti. Se si decide di rimuovere un campo esistente o renderlo di sola lettura, si tratta di una modifica compatibile con le versioni precedenti se il server ignora tali campi e i vecchi client continueranno a funzionare. Tuttavia, se si esegue una convalida rigorosa sul contenuto, è non compatibile con le versioni precedenti e i vecchi client cesseranno di funzionare. Il primo generalmente significa meno lavoro sia per il maintainer di un'API che per i suoi client.

    
risposta data 14.09.2013 - 03:49
fonte
9

Potenza idem

Seguendo la RFC, un PUT dovrebbe consegnare l'oggetto completo alla risorsa. La ragione principale di ciò è che PUT dovrebbe essere idempotente. Ciò significa che una richiesta, che viene ripetuta, dovrebbe valutare lo stesso risultato sul server.

Se consenti aggiornamenti parziali, non può più essere idem-potente. Se hai due clienti. Client A e B, quindi il seguente scenario può evolvere:

Il cliente A ottiene un'immagine dalle immagini delle risorse. Questo contiene una descrizione dell'immagine, che è ancora valida. Il cliente B inserisce una nuova immagine e aggiorna la descrizione di conseguenza. L'immagine è cambiata. Il cliente A vede, non deve cambiare la descrizione, perché è come desidera e mette solo l'immagine.

Questo porterà a un'incoerenza, l'immagine ha i metadati sbagliati allegati!

Ancora più fastidioso è che qualsiasi intermediario può ripetere la richiesta. Nel caso in cui decida in qualche modo il PUT fallito.

Il significato di PUT non può essere modificato (sebbene tu possa abusarne).

Altre opzioni

Fortunatamente c'è un'altra opzione, questa è PATCH. PATCH è un metodo che consente di aggiornare parzialmente una struttura. Puoi semplicemente inviare una struttura parziale. Per le applicazioni semplici, va bene. Questo metodo non è garantito come idem potente. Il client deve inviare una richiesta nel seguente formato:

PATCH /file.txt HTTP/1.1
Host: www.example.com
Content-Type: application/example
If-Match: "e0023aa4e"
Content-Length: 20
{fielda: 1, fieldc: 2}

E il server può rispondere con 204 (Nessun contenuto) per segnalare il successo. In caso di errore non è possibile aggiornare una parte della struttura. Il metodo PATCH è atomico.

Lo svantaggio di questo metodo è che non tutti i browser supportano questo, ma questa è l'opzione più naturale in un servizio REST.

Esempio di richiesta di patch: link

Patch Json

L'opzione JSON sembra essere abbastanza completa e un'opzione interessante. Ma può essere difficile da implementare per terze parti. Devi decidere se la tua base di utenti può gestirlo.

È anche un po 'complicato, perché è necessario creare un piccolo interprete che converte i comandi in una struttura parziale, che si intende utilizzare per aggiornare il modello. Questo interprete dovrebbe anche controllare, se i comandi forniti hanno un senso. Alcuni comandi si annullano a vicenda. (scrivi fielda, cancella fielda). Penso che tu voglia riportarlo al cliente per limitare il tempo di debug dalla sua parte.

Ma se hai tempo, questa è una soluzione davvero elegante. Dovresti comunque convalidare i campi, ovviamente. È possibile combinare questo con il metodo PATCH per rimanere nel modello REST. Ma penso che il POST sia accettabile qui.

Non funziona male

Se decidi di andare con l'opzione PUT, che è piuttosto rischioso. Quindi non dovresti almeno scartare l'errore. L'utente ha una certa aspettativa (i dati saranno aggiornati) e se interrompi questo, darai a alcuni sviluppatori non un buon momento.

Potresti scegliere di tornare indietro: 409 Conflict o 403 Forbidden. Dipende da come si guarda al processo di aggiornamento. Se la vedi come un insieme di regole (incentrate sul sistema), allora il conflitto sarà più bello. Qualcosa di simile, questi campi non sono aggiornabili. (In conflitto con le regole). Se lo vedi come un problema di autorizzazione (incentrato sull'utente), allora dovresti tornare proibito. Con: non sei autorizzato a cambiare questi campi.

Dovresti ancora forzare gli utenti a inviare tutti i campi modificabili.

Un'opzione ragionevole per far rispettare questo è impostarla su una sotto-risorsa, che offre solo i dati modificabili.

Opinione personale

Personalmente andrei (se non dovessi lavorare con i browser) per il semplice modello PATCH e poi lo estenderò con un processore patch JSON. Questo può essere fatto differenziando sui mimetipi: Il tipo mime di patch json:

application / json-patch

E JSON:    application / json-patch

facilita l'implementazione in due fasi.

    
risposta data 15.08.2013 - 00:38
fonte

Leggi altre domande sui tag