RIPOSO: inserire gli ID nel corpo o no?


96

Diciamo che voglio avere una risorsa RESTful per le persone, in cui il client è in grado di assegnare l'ID.

Una persona ha questo aspetto: {"id": <UUID>, "name": "Jimmy"}

Ora, come dovrebbe il client salvarlo (o "Metterlo")?

  1. PUT /person/UUID {"id": <UUID>, "name": "Jimmy"} - ora abbiamo questa brutta duplicazione che dobbiamo verificare continuamente: l'ID nel corpo corrisponde a quello nel percorso?
  2. Rappresentazione asimmetrica:
    • PUT /person/UUID {"name": "Jimmy"}
    • GET /person/UUID ritorna {"id": <UUID>, "name": "Jimmy"}
  3. Nessun ID nel corpo - ID solo nella posizione:
    • PUT /person/UUID {"name": "Jimmy"}
    • GET /person/UUID ritorna {"name": "Jimmy"}
  4. Nessun tipo POSTsembra una buona idea poiché l'ID è generato dal client.

Quali sono i modelli comuni e i modi per risolverlo? Gli ID solo in posizione sembrano il modo più dogmaticamente corretto, ma rendono anche più difficile l'implementazione pratica.

Risposte:


62

Non c'è niente di sbagliato nell'avere diversi modelli di lettura / scrittura: il client può scrivere una rappresentazione di risorsa dove dopo il server può restituire un'altra rappresentazione con elementi aggiunti / calcolati in essa (o anche una rappresentazione completamente diversa - non c'è nulla in nessuna specifica contro quella , l'unico requisito è che PUT crei o sostituisca la risorsa).

Quindi sceglierei la soluzione asimmetrica in (2) ed eviterei il "brutto controllo della duplicazione" sul lato server durante la scrittura:

PUT /person/UUID {"name": "Jimmy"}

GET /person/UUID returns {"id": <UUID>, "name": "Jimmy"}

2
E se applichi la digitazione (statica o dinamica) non puoi avere indolore modelli senza ID ... Quindi è molto più facile rimuovere l'ID dall'URL per le richieste PUT. Non sarà "riposante" ma sarà corretto.
Ivan Kleshnin

2
Mantieni TO aggiuntivo senza idinsieme a TO con ID ed entità e convertitori aggiuntivi e overhead troppo grande per i programmatori.
Grigory Kislin

E se ottengo l'ID da BODY es .: PUT / person {"id": 1, "name": "Jimmy"}. Sarebbe una cattiva pratica?
Bruno Santos

Mettere l'ID nel corpo andrebbe bene. Usa un GUID per ID invece di un numero intero, altrimenti corri il rischio di ID duplicati.
Jørn Wildt

Questo è sbagliato. Vedi la mia risposta. PUT deve contenere l'intera risorsa. Usa PATCH se desideri escludere l'id e aggiornare solo parti del record.
CompEng88

27

Se si tratta di un'API pubblica, dovresti essere prudente quando rispondi, ma accetta liberamente.

Con questo voglio dire, dovresti supportare sia 1 che 2. Sono d'accordo che 3 non ha senso.

Il modo per supportare sia 1 che 2 è ottenere l'ID dall'URL se non ne viene fornito nessuno nel corpo della richiesta e, se è nel corpo della richiesta, verificare che corrisponda all'ID nell'URL. Se i due non corrispondono, restituisci una risposta 400 Bad Request.

Quando restituisci una risorsa persona sii prudente e includi sempre l'id nel json, anche se è opzionale nel put.


3
Questa dovrebbe essere la soluzione accettata. Un'API dovrebbe essere sempre user-friendly. Dovrebbe essere facoltativo nel corpo. Non dovrei ricevere un ID da un POST e quindi devo renderlo indefinito in un PUT. Inoltre, il punto di risposta 400 indicato è corretto.
Michael,

Circa 400 codici vedi anche softwareengineering.stackexchange.com/questions/329229/… discussione. In breve, un codice 400 non è inappropriato, solo meno specifico, rispetto a 422.
Grigory Kislin

8

Una soluzione a questo problema implica il concetto un po 'confuso di "Hypertext As The Engine Of Application State" o "HATEOAS". Ciò significa che una risposta REST contiene le risorse o le azioni disponibili da eseguire come collegamenti ipertestuali. Utilizzando questo metodo, che faceva parte della concezione originale di REST, gli identificatori / ID univoci delle risorse sono essi stessi collegamenti ipertestuali. Quindi, ad esempio, potresti avere qualcosa come:

GET /person/<UUID> {"person": {"location": "/person/<UUID>", "data": { "name": "Jimmy"}}}

Quindi, se vuoi aggiornare quella risorsa, potresti fare (pseudocodice):

updatedPerson = person.data
updatedPerson.name = "Timmy"
PUT(URI: response.resource, data: updatedPerson)

Un vantaggio di ciò è che il client non deve avere alcuna idea della rappresentazione interna del server degli ID utente. Gli ID potrebbero cambiare e anche gli URL stessi potrebbero cambiare, purché il client abbia un modo per scoprirli. Ad esempio, quando ottieni una raccolta di persone, potresti restituire una risposta come questa:

GET /people
{ "people": [
    "/person/1",
    "/person/2"
  ]
}

(Potresti, ovviamente, restituire anche l'oggetto persona completo per ogni persona, a seconda delle esigenze dell'applicazione).

Con questo metodo, pensi ai tuoi oggetti più in termini di risorse e posizioni e meno in termini di ID. La rappresentazione interna dell'identificatore univoco è quindi disaccoppiata dalla logica del client. Questo è stato l'impulso originale dietro REST: creare architetture client-server che sono accoppiate in modo più lasco rispetto ai sistemi RPC che esistevano prima, utilizzando le funzionalità di HTTP. Per ulteriori informazioni su HATEOAS, guarda l' articolo di Wikipedia e questo breve articolo .


4

In un inserto non è necessario aggiungere l'id nell'URL. In questo modo se invii un ID in un PUT potresti interpretarlo come un UPDATE per cambiare la chiave primaria.

  1. INSERIRE:

    PUT /persons/ 
      {"id": 1, "name": "Jimmy"}
    HTTP/1.1 201 Created     
      {"id": 1, "name": "Jimmy", "other_field"="filled_by_server"}
    
    GET /persons/1
    
    HTTP/1.1 200 OK
      {"id": 1, "name": "Jimmy", "other_field"="filled_by_server"}  
    
  2. AGGIORNARE

    PUT /persons/1 
         {"id": "2", "name": "Jimmy Jr"} - 
    HTTP/1.1 200 OK
         {"id": "2", "name": "Jimmy Jr", "other_field"="filled_by_server"}
    
    GET /persons/2 
    
    HTTP/1.1 200 OK
         {"id": "2", "name": "Jimmy Jr", "other_field"="updated_by_server"}
    

L' API JSON utilizza questo standard e risolve alcuni problemi restituendo l'oggetto inserito o aggiornato con un collegamento al nuovo oggetto. Alcuni aggiornamenti o inserti possono includere una logica aziendale che modificherà i campi aggiuntivi

Vedrai anche che puoi evitare il recupero dopo l'inserimento e l'aggiornamento.



2

Sebbene sia corretto avere rappresentazioni diverse per operazioni diverse, una raccomandazione generale per PUT è di contenere il carico utile INTERO . Ciò significa che iddovrebbe esserci anche quello. Altrimenti, dovresti usare PATCH.

Detto questo, penso che PUT dovrebbe essere utilizzato principalmente per gli aggiornamenti e iddovrebbe essere sempre passato anche nell'URL. Di conseguenza, l'utilizzo di PUT per aggiornare l'identificatore della risorsa è una cattiva idea. Ci lascia in una situazione indesiderabile quando idnell'URL può essere diverso da idnel corpo.

Allora, come risolviamo un tale conflitto? Fondamentalmente abbiamo 2 opzioni:

  • lancia un'eccezione 4XX
  • aggiungi un'intestazione Warning( X-API-Warnecc.).

È il più vicino possibile a rispondere a questa domanda perché l'argomento in generale è una questione di opinione.


2

Cordiali saluti, le risposte qui sono sbagliate.

Vedere:

https://restfulapi.net/rest-api-design-tutorial-with-example/

https://restfulapi.net/rest-put-vs-post/

https://restfulapi.net/http-methods/#patch

METTERE

Utilizza le API PUT principalmente per aggiornare la risorsa esistente (se la risorsa non esiste, l'API può decidere di creare una nuova risorsa o meno). Se una nuova risorsa è stata creata dall'API PUT, il server di origine DEVE informare l'agente utente tramite la risposta del codice di risposta HTTP 201 (Creato) e se una risorsa esistente viene modificata, 200 (OK) o 204 (Nessun contenuto) codici di risposta DOVREBBERO essere inviati per indicare il completamento con successo della richiesta.

Se la richiesta passa attraverso una cache e l'URI della richiesta identifica una o più entità attualmente memorizzate nella cache, tali voci DOVREBBERO essere trattate come obsolete. Le risposte a questo metodo non sono memorizzabili nella cache.

Utilizzare PUT quando si desidera modificare una singola risorsa che fa già parte della raccolta di risorse. PUT sostituisce la risorsa nella sua interezza. Usa PATCH se la richiesta aggiorna parte della risorsa.

PATCH

Le richieste HTTP PATCH servono per effettuare un aggiornamento parziale su una risorsa. Se vedi richieste PUT anche modificare un'entità risorsa in modo da rendere più chiaro - il metodo PATCH è la scelta corretta per aggiornare parzialmente una risorsa esistente e PUT dovrebbe essere usato solo se stai sostituendo una risorsa nella sua interezza.

Quindi dovresti usarlo in questo modo:

POST    /device-management/devices      : Create a new device
PUT     /device-management/devices/{id} : Update the device information identified by "id"
PATCH   /device-management/devices/{id} : Partial-update the device information identified by "id"

Le pratiche RESTful indicano che non dovrebbe importare cosa Metti in / {id} - il contenuto del record dovrebbe essere aggiornato a quello fornito dal payload - ma GET / {id} dovrebbe comunque collegarsi alla stessa risorsa.

In altre parole, PUT / 3 potrebbe aggiornarsi all'id payload a 4, ma GET / 3 dovrebbe comunque collegarsi allo stesso payload (e restituire quello con id impostato su 4).

Se stai decidendo che la tua API richiede lo stesso identificatore nell'URI e nel payload, è tuo compito assicurarti che corrisponda, ma usa sicuramente PATCH invece di PUT se stai escludendo l'id nel payload che dovrebbe essere lì nella sua interezza . È qui che la risposta accettata ha sbagliato. PUT deve sostituire l'intera risorsa, dove la patch può essere parziale.


1

Non c'è niente di male nell'usare approcci diversi. ma penso che il modo migliore sia la soluzione con 2nd .

 PUT /person/UUID {"name": "Jimmy"}

 GET /person/UUID returns {"id": <UUID>, "name": "Jimmy"}

viene utilizzato principalmente in questo modo anche il framework dell'entità usa questa tecnica quando l'entità viene aggiunta in dbContext la classe senza l'ID generato è l'ID generato dal riferimento in Entity Framework.


1

Lo sto guardando da un punto di vista JSON-LD / Web semantico perché è un buon modo per ottenere una reale conformità REST come ho delineato in queste diapositive . Guardandolo da quella prospettiva, non ci sono dubbi sull'opzione (1.) poiché l'ID (IRI) di una risorsa Web dovrebbe essere sempre uguale all'URL che posso utilizzare per cercare / dereferenziare la risorsa. Penso che la verifica non sia veramente difficile da implementare né intensa dal punto di vista computazionale; quindi non lo considero un motivo valido per scegliere l'opzione (2.). Penso che l'opzione (3.) non sia realmente un'opzione in quanto POST (crea nuovo) ha una semantica diversa da PUT (aggiorna / sostituisci).


0

Potrebbe essere necessario esaminare i tipi di richiesta PATCH / PUT.

Le richieste PATCH vengono utilizzate per aggiornare parzialmente una risorsa mentre nelle richieste PUT, devi inviare l'intera risorsa dove viene sovrascritta sul server.

Per quanto riguarda avere un ID nell'URL, penso che dovresti sempre averlo in quanto è una pratica standard identificare una risorsa. Anche l'API Stripe funziona in questo modo.

È possibile utilizzare una richiesta PATCH per aggiornare una risorsa sul server con ID per identificarla ma non aggiornare l'ID effettivo.

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.