API RESTful. Devo restituire l'oggetto che è stato creato / aggiornato?


36

Sto progettando un servizio Web RESTful utilizzando WebApi e mi chiedevo quali risposte HTTP e quali corpi di risposta restituire durante l'aggiornamento / la creazione di oggetti.

Ad esempio, posso usare il metodo POST per inviare alcuni JSON al servizio Web e quindi creare un oggetto. È consigliabile impostare quindi lo stato HTTP su creato (201) o ok (200) e semplicemente restituire un messaggio come "Nuovo dipendente aggiunto" o restituire l'oggetto che è stato inviato originariamente?

Lo stesso vale per il metodo PUT. Quale stato HTTP è meglio usare e devo restituire l'oggetto che è stato creato o solo un messaggio? Considerando il fatto che l'utente conosce comunque l'oggetto che sta cercando di creare / aggiornare.

qualche idea?

Esempio:

Aggiungi nuovo dipendente:

POST /api/employee HTTP/1.1
Host: localhost:8000
Content-Type: application/json

{
    "Employee": {
        "Name" : "Joe Bloggs",
        "Department" : "Finance"
    }
}

Aggiorna dipendente esistente:

PUT /api/employee HTTP/1.1
Host: localhost:8000
Content-Type: application/json

{
    "Employee": {
        "Id" : 1
        "Name" : "Joe Bloggs",
        "Department" : "IT"
    }
}

risposte:

Risposta con oggetto creato / aggiornato

HTTP/1.1 201 Created
Content-Length: 39
Content-Type: application/json; charset=utf-8
Date: Mon, 28 Mar 2016 14:32:39 GMT

{
    "Employee": {
        "Id" : 1
        "Name" : "Joe Bloggs",
        "Department" : "IT"
    }
}

Risposta con solo messaggio:

HTTP/1.1 200 OK
Content-Length: 39
Content-Type: application/json; charset=utf-8
Date: Mon, 28 Mar 2016 14:32:39 GMT

{
    "Message": "Employee updated"
}

Risposta con solo codice di stato:

HTTP/1.1 204 No Content
Content-Length: 39
Date: Mon, 28 Mar 2016 14:32:39 GMT

2
Buona domanda, ma usare il termine "best practice" è una sorta di tabù su questo sito meta.programmers.stackexchange.com/questions/2442/… Potresti voler semplicemente riformulare la domanda. meta.programmers.stackexchange.com/questions/6967/…
Snoop

3
Come follow-up, sarebbe una buona idea avere un flag nella richiesta in modo che (ad esempio) un'applicazione mobile possa restituire l'intero oggetto quando su WiFi, ma solo l'ID quando si utilizzano i dati cellulari? Esiste un'intestazione che dovrebbe essere utilizzata per evitare l'inquinamento del JSON?
Andrew dice di reintegrare Monica il

@AndrewPiliser Idea interessante, anche se personalmente ritengo sia meglio scegliere un approccio e attenersi ad esso. Quindi, man mano che la tua applicazione si sviluppa o diventa più popolare, ottimizzala
iswinky

@AndrewPiliser la tua idea è molto simile alla UPDATE/INSERT ... RETURNINGvariante Postgresql per SQL. È estremamente utile, soprattutto perché mantiene l'invio di nuovi dati e la richiesta per la versione aggiornata atomica.
Beldaz,

Risposte:


31

Come con la maggior parte delle cose, dipende. Il tuo compromesso è la facilità d'uso rispetto alle dimensioni della rete. Può essere molto utile per i clienti vedere la risorsa creata. Può includere campi popolati dal server, ad esempio ora ultima creazione. Poiché sembra che tu includa idinvece di utilizzare hateoas, i client probabilmente vorranno vedere l'id per la risorsa che hanno appena modificato POST.

Se non includi la risorsa creata, ti preghiamo di non creare un messaggio arbitrario. I campi 2xx e Posizione sono informazioni sufficienti per consentire ai clienti di essere sicuri che la loro richiesta sia stata gestita correttamente.


+1 L'obiettivo di hateo di non consentire al client di comporre gli uri può essere raggiunto anche consentendo al client di compilare i modelli URL forniti dal server con ID specifici. Sì, il client "compone" ma solo in un modo "riempi gli spazi". Sebbene non sia puro HATEOAS, raggiunge l'obiettivo e rende il lavoro con oggetti che hanno un (grande) numero di "azioni" uri è un po 'meno sensibile alla larghezza di banda, per non parlare di quando li metti in un elenco (ampio).
Marjan Venema,

3
+1 sul consiglio "per favore non creare un messaggio arbitrario"
HairOfTheDog,

Il "nessun messaggio arbitrario" si concentra sui messaggi di stringa o su qualsiasi valore di ritorno che non è la risorsa creata? Mi sto concentrando sui casi in cui restituiamo l'ID della risorsa creata (ma non la risorsa stessa) e mi chiedevo dove questo si adatta.
Flater

12

Personalmente, torno sempre solo 200 OK.

Per citare la tua domanda

Considerando il fatto che l'utente conosce comunque l'oggetto che sta cercando di creare / aggiornare.

Perché aggiungere ulteriore larghezza di banda (che potrebbe essere necessario pagare) per dire al cliente ciò che già sa?


1
Questo è quello che stavo pensando, se non è valido puoi restituire messaggi di convalida, ma se è valido e viene creato / aggiornato, controlla il codice di stato HTTP e mostra all'utente un messaggio, ad esempio "Evviva" basato su quello
iswinky

3
Vedere stackoverflow.com/a/827045/290182 in merito a 200/ 204 No Contentper evitare confusione tra jQuery e simili.
Beldaz,

10

@iswinky Restituirei sempre il payload in caso sia di POST che di PUT.

In caso di POST è possibile creare l'entità con un ID interno o un UUID. Quindi ha senso restituire il payload.

Allo stesso modo in caso di PUT, potresti ignorare alcuni campi dell'utente (valori immutabili, diciamo), o in caso di PATCH, i dati potrebbero essere stati modificati anche da altri utenti.

Il rinvio dei dati nel modo in cui è stato persistito è sempre una buona idea e sicuramente non danneggia. Se il chiamante non ha bisogno di questi dati restituiti, non li elaborerà ma consumerà semplicemente StatusCode. Altrimenti possono utilizzare questi dati come qualcosa con cui aggiornare l'interfaccia utente.

È solo nel caso di un DELETE che non rispedirei il payload e farei un 200 con un contenuto di risposta o un 204 senza un contenuto di risposta.

Modifica: grazie ad alcuni commenti dal basso, sto riformulando la mia risposta. Sono ancora fedele al modo in cui disegno le mie API e invio risposte, ma penso che abbia senso qualificare alcuni dei miei pensieri di progettazione.

a) Quando dico rispedire il payload, in realtà intendevo dire rispedire i dati della risorsa, non lo stesso payload fornito nella richiesta. Es .: se invii un payload di creazione, nel back-end posso creare altre entità come UUID e (forse) timestamp e anche alcune connessioni (grafiche). Manderei tutto questo nella risposta (non solo il payload della richiesta così com'è - il che è inutile).

b) Non rispedirei risposte nel caso in cui il payload sia molto grande. Ne ho discusso nei commenti, ma ciò che vorrei avvertire è che farei del mio meglio per progettare le mie API o le mie risorse in modo che non debbano avere payload molto grandi. Proverei a suddividere le risorse in entità più piccole e gestibili in modo tale che ogni risorsa sia definita da 15-20 attributi JSON e non più grande.

Nel caso in cui l'oggetto sia molto grande o l'oggetto genitore sia in fase di aggiornamento, rispedirei le strutture nidificate come hrefs.

In conclusione, proverei sicuramente a inviare indietro i dati che hanno senso per il consumatore / UI da elaborare immediatamente ed essere fatti con un'azione API atomica piuttosto che dover andare a recuperare 2-5 API in più solo per aggiornare l'interfaccia utente post creazione / aggiornamento dei dati.

Le API da server a server potrebbero pensarla diversamente. Mi sto concentrando sulle API che guidano l'esperienza dell'utente.


Riesco a vedere molte situazioni in cui rispedire l'intero payload è una cattiva idea, quando il payload è grande.
Beldaz,

2
@beldaz è completamente d'accordo. YMMV basato sul design dell'API REST. In genere evito oggetti molto grandi e li scompongo in una serie di risorse secondarie / PUT. Se il payload è molto grande, ci sono modi migliori per farlo, e qui dovresti fare HATEOAS (come dice Marjan sopra) in cui restituisci il riferimento all'oggetto anziché all'oggetto stesso.
ksprashu,

@ksprashu: "Quindi ha senso restituire il payload" - Trovo che questa sia una cattiva idea, perché così una risorsa può essere ottenuta in molti modi: tramite GET, come risposta di POST, come risposta di PUT. Significa che il client ottiene 3 risorse con una struttura potenzialmente diversa . Dove, come se si restituisse solo l'URI (posizione), senza corpo, l'unico modo per ottenere una risorsa sarebbe OTTENERE. Ciò assicurerebbe che il client ottenga sempre risposte coerenti.
Mentallurg,

@mentallurg Mi rendo conto che potrei non aver formulato questo diritto. Grazie per segnalarlo. Ho modificato la mia risposta. Fammi sapere se questo ha più senso.
ksprashu,

Finché si implementa un servizio per il lavoro a casa, non importa. Fallo come preferisci. Risparmia tempo.
mentallurg

9

Facendo riferimento agli standard del collegamento RFC , è necessario restituire lo stato 201 (creato) dopo aver archiviato correttamente la risorsa richiesta utilizzando Posta. Nella maggior parte delle applicazioni, l'id della risorsa viene generato dal server stesso, quindi è buona norma restituire l'id della risorsa creata. Restituire l'intero oggetto è l'overhead per la richiesta di post. La pratica ideale è di restituire la posizione dell'URL della risorsa appena creata.

Ad esempio, è possibile fare riferimento all'esempio seguente che salva l'oggetto Employee nel database e restituisce l'URL dell'oggetto risorsa appena creato come risposta.

@RequestMapping(path = "/employees", method = RequestMethod.POST)
public ResponseEntity<Object> saveEmployee(@RequestBody Employee employee) {
        int id = employeeService.saveEmployee(employee);
        URI uri = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(id).toUri();
        return ResponseEntity.created(uri).build();
}

Questo endpoint di riposo restituirà la risposta come:

Stato 201 - CREATO

Posizione intestazione → http: // localhost: 8080 / impiegati / 1


Bello e pulito - seguirò questo da ora in poi
Hassan Tareq il

0

Vorrei subordinare un payload nel corpo di ritorno a un parametro HTTP.

Il più delle volte è meglio restituire una sorta di contenuto al consumatore API per evitare inutili round trip (uno dei motivi per cui GraphQL esiste.)

È un dato di fatto, poiché le nostre applicazioni diventano più intense e distribuite in termini di dati, cerco di osservare queste linee guida:

La mia linea guida :

Ogni volta che c'è un caso d'uso che richiede un GET immediatamente dopo un POST o PUT, è un caso in cui potrebbe essere meglio restituire semplicemente qualcosa nella risposta POST / PUT.

Come viene fatto e quale tipo di contenuto per tornare indietro da un PUT o POST, è specifico dell'applicazione. Ora, sarebbe interessante se l'applicazione potesse parametrizzare il tipo di "contenuto" nel corpo della risposta (vogliamo solo la posizione del nuovo oggetto, o alcuni dei campi, o l'intero oggetto, ecc.)

Un'applicazione potrebbe definire un insieme di parametri che un POST / PUT può ricevere per controllare il tipo di "contenuto" da restituire nel corpo della risposta. Oppure potrebbe codificare una sorta di query GraphQL come parametro (posso vedere che ciò è utile ma può anche diventare un incubo di manutenzione).

Ad ogni modo, mi sembra che:

  1. È OK (e molto probabilmente desiderabile) restituire qualcosa in un corpo di risposta POST / PUT.
  2. Come questo è specifico per l'applicazione e quasi impossibile da generalizzare.
  3. Non si desidera restituire un "contesto" di grandi dimensioni per impostazione predefinita (il rumore del traffico che sconfigge l'intero motivo dell'allontanamento da POST seguito da GET).

Quindi, 1) fallo, ma 2) mantienilo semplice.

Un'altra opzione che ho visto sono le persone che creano endpoint alternativi (diciamo, clienti per POST / PUT che non restituiscono nulla nel corpo e / customer_with_details per POST / PUT a / clienti, ma che restituiscono qualcosa nel corpo della risposta.)

Eviterei questo approccio, comunque. Cosa succede quando è necessario legittimamente restituire diversi tipi di contenuto? Un endpoint per tipo di contenuto? Non scalabile o mantenibile.

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.