REST - IDs in den Körper stecken oder nicht?

95

Angenommen, ich möchte eine RESTful-Ressource für Personen haben, in der der Client eine ID zuweisen kann.

Eine Person sieht so aus: {"id": <UUID>, "name": "Jimmy"}

Wie soll der Client es nun speichern (oder "PUT")?

  1. PUT /person/UUID {"id": <UUID>, "name": "Jimmy"} - Jetzt haben wir diese böse Vervielfältigung, die wir ständig überprüfen müssen: Stimmt die ID im Text mit der im Pfad überein?
  2. Asymmetrische Darstellung:
    • PUT /person/UUID {"name": "Jimmy"}
    • GET /person/UUID kehrt zurück {"id": <UUID>, "name": "Jimmy"}
  3. Keine IDs im Körper - ID nur vor Ort:
    • PUT /person/UUID {"name": "Jimmy"}
    • GET /person/UUID kehrt zurück {"name": "Jimmy"}
  4. Keine Art POSTscheint eine gute Idee zu sein, da die ID vom Client generiert wird.

Was sind die gängigen Muster und Lösungswege? IDs nur vor Ort scheinen der dogmatisch korrekteste Weg zu sein, erschweren aber auch die praktische Implementierung.

Konrad Garus
quelle

Antworten:

62

Es ist nichts Falsches daran, unterschiedliche Lese- / Schreibmodelle zu haben: Der Client kann eine Ressourcendarstellung schreiben, wobei der Server eine andere Darstellung mit hinzugefügten / berechneten Elementen zurückgeben kann (oder sogar eine völlig andere Darstellung - dagegen gibt es in keiner Spezifikation etwas dagegen Die einzige Voraussetzung ist, dass PUT die Ressource erstellt oder ersetzt.

Also würde ich mich für die asymmetrische Lösung in (2) entscheiden und die "böse Duplizierungsprüfung" auf der Serverseite beim Schreiben vermeiden:

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

GET /person/UUID returns {"id": <UUID>, "name": "Jimmy"}
Jørn Wildt
quelle
1
Und wenn Sie die Eingabe (statisch oder dynamisch) anwenden, können Sie keine Modelle ohne ID ohne Probleme haben ... Es ist also viel einfacher, die ID für PUT-Anforderungen von der URL zu entfernen. Es wird nicht "erholsam" sein, aber es wird richtig sein.
Ivan Kleshnin
1
Behalten Sie zusätzliches TO ohne idzusammen mit TO mit ID und Entität und zusätzlichen Konvertern und zu viel Overhead für Programmierer.
Grigory Kislin
Was ist, wenn ich die ID von BODY bekomme, z. B.: PUT / person {"id": 1, "name": "Jimmy"}. Wäre das eine schlechte Praxis?
Bruno Santos
Das Einfügen des Ausweises in den Körper wäre in Ordnung. Verwenden Sie eine GUID für ID anstelle einer Ganzzahl. Andernfalls besteht das Risiko, dass doppelte IDs vorhanden sind.
Jørn Wildt
Das ist falsch. Siehe meine Antwort. PUT muss die gesamte Ressource enthalten. Verwenden Sie PATCH, wenn Sie die ID ausschließen und nur Teile des Datensatzes aktualisieren möchten.
CompEng88
26

Wenn es sich um eine öffentliche API handelt, sollten Sie bei der Antwort konservativ sein, aber großzügig akzeptieren.

Damit meine ich, dass Sie sowohl 1 als auch 2 unterstützen sollten. Ich stimme zu, dass 3 keinen Sinn ergibt.

Die Möglichkeit, sowohl 1 als auch 2 zu unterstützen, besteht darin, die ID aus der URL abzurufen, wenn im Anforderungshauptteil keine angegeben ist, und wenn sie sich im Anforderungshauptteil befindet, zu überprüfen, ob sie mit der ID in der URL übereinstimmt. Wenn die beiden nicht übereinstimmen, geben Sie eine 400 Bad Request-Antwort zurück.

Seien Sie bei der Rückgabe einer Personenressource konservativ und geben Sie die ID immer im json an, auch wenn sie im put optional ist.

Jay Pete
quelle
3
Dies sollte die akzeptierte Lösung sein. Eine API sollte immer benutzerfreundlich sein. Es sollte im Körper optional sein. Ich sollte keine ID von einem POST erhalten und muss sie dann in einem PUT undefiniert machen. Auch der 400-Antwortpunkt ist richtig eingeschaltet.
Michael
Über 400 Code siehe auch softwareengineering.stackexchange.com/questions/329229/… Diskussion. Kurz gesagt, ein 400er Code ist nicht unangemessen, nur weniger spezifisch im Vergleich zu 422.
Grigory Kislin
8

Eine Lösung für dieses Problem ist das etwas verwirrende Konzept von "Hypertext als Motor des Anwendungsstatus" oder "HATEOAS". Dies bedeutet, dass eine REST-Antwort die verfügbaren Ressourcen oder Aktionen enthält, die als Hyperlinks ausgeführt werden sollen. Bei Verwendung dieser Methode, die Teil der ursprünglichen Konzeption von REST war, sind die eindeutigen Kennungen / IDs von Ressourcen selbst Hyperlinks. So könnten Sie zum Beispiel Folgendes haben:

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

Wenn Sie diese Ressource aktualisieren möchten, können Sie Folgendes tun (Pseudocode):

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

Ein Vorteil davon ist, dass der Client keine Ahnung von der internen Darstellung der Benutzer-IDs durch den Server haben muss. Die IDs können sich ändern, und sogar die URLs selbst können sich ändern, solange der Client die Möglichkeit hat, sie zu erkennen. Wenn Sie beispielsweise eine Sammlung von Personen abrufen, können Sie eine Antwort wie die folgende zurückgeben:

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

(Sie können natürlich auch das vollständige Personenobjekt für jede Person zurückgeben, abhängig von den Anforderungen der Anwendung.)

Bei dieser Methode denken Sie mehr an Ihre Objekte in Bezug auf Ressourcen und Standorte und weniger an ID. Die interne Darstellung der eindeutigen Kennung ist somit von Ihrer Client-Logik entkoppelt. Dies war der ursprüngliche Anstoß für REST: mithilfe der Funktionen von HTTP Client-Server-Architekturen zu erstellen, die lockerer gekoppelt sind als die zuvor existierenden RPC-Systeme. Weitere Informationen zu HATEOAS finden Sie im Wikipedia-Artikel sowie in diesem kurzen Artikel .

bthecohen
quelle
4

In einer Einfügung müssen Sie die ID nicht in die URL einfügen. Auf diese Weise können Sie beim Senden einer ID in einem PUT als UPDATE interpretiert werden, um den Primärschlüssel zu ändern.

  1. EINFÜGEN:

    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. AKTUALISIEREN

    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"}
    

Die JSON-API verwendet diesen Standard und löst einige Probleme, die das eingefügte oder aktualisierte Objekt mit einem Link zum neuen Objekt zurückgeben. Einige Aktualisierungen oder Einfügungen enthalten möglicherweise eine Geschäftslogik, die zusätzliche Felder ändert

Sie werden auch sehen, dass Sie das Abrufen nach dem Einfügen und Aktualisieren vermeiden können.

Borjab
quelle
3

Dies wurde schon einmal gefragt - die Diskussion ist einen Blick wert:

Sollte eine RESTful GET-Antwort die ID einer Ressource zurückgeben?

Dies ist eine dieser Fragen, bei denen es leicht ist, sich in eine Debatte darüber zu verstricken, was "RESTful" ist und was nicht .

Für das, was es wert ist, versuche ich, in konsistenten Ressourcen zu denken und das Design zwischen den Methoden nicht zu ändern. IMHO das Wichtigste aus Sicht der Benutzerfreundlichkeit ist jedoch, dass Sie über die gesamte API konsistent sind!

Ben Morris
quelle
2

Während es in Ordnung ist, unterschiedliche Darstellungen für unterschiedliche Operationen zu haben, besteht eine allgemeine Empfehlung für PUT darin, die GANZE Nutzlast zu enthalten . Das heißt, das idsollte auch da sein. Andernfalls sollten Sie PATCH verwenden.

Trotzdem denke ich, dass PUT hauptsächlich für Updates verwendet werden sollte und das idimmer auch in der URL übergeben werden sollte. Infolgedessen ist die Verwendung von PUT zum Aktualisieren der Ressourcen-ID eine schlechte Idee. Es bringt uns in eine unerwünschte Situation, wenn sich iddie URL von der idim Körper unterscheiden kann.

Wie lösen wir einen solchen Konflikt? Wir haben grundsätzlich 2 Möglichkeiten:

  • eine 4XX-Ausnahme auslösen
  • Fügen Sie einen Warning( X-API-Warnetc) Header hinzu.

Das ist so nah wie möglich an der Beantwortung dieser Frage, da das Thema im Allgemeinen Ansichtssache ist.

Yuranos
quelle
2

Nur zu Ihrer Information, die Antworten hier sind falsch.

Sehen:

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

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

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

STELLEN

Verwenden Sie PUT-APIs hauptsächlich zum Aktualisieren vorhandener Ressourcen (wenn die Ressource nicht vorhanden ist, entscheidet die API möglicherweise, ob eine neue Ressource erstellt werden soll oder nicht). Wenn eine neue Ressource von der PUT-API erstellt wurde, MUSS der Ursprungsserver den Benutzeragenten über die Antwort des HTTP-Antwortcodes 201 (Erstellt) informieren. Wenn eine vorhandene Ressource geändert wird, entweder die Antwort 200 (OK) oder 204 (Kein Inhalt). Antwortcodes MÜSSEN gesendet werden, um den erfolgreichen Abschluss der Anforderung anzuzeigen.

Wenn die Anforderung einen Cache durchläuft und der Anforderungs-URI eine oder mehrere aktuell zwischengespeicherte Entitäten identifiziert, MÜSSEN diese Einträge als veraltet behandelt werden. Antworten auf diese Methode können nicht zwischengespeichert werden.

Verwenden Sie PUT, wenn Sie eine einzelne Ressource ändern möchten, die bereits Teil der Ressourcensammlung ist. PUT ersetzt die Ressource vollständig. Verwenden Sie PATCH, wenn die Anforderung einen Teil der Ressource aktualisiert.

PATCH

HTTP-PATCH-Anforderungen müssen eine Ressource teilweise aktualisieren. Wenn Sie sehen, dass PUT-Anforderungen auch eine Ressourcenentität ändern, um dies klarer zu machen, ist die PATCH-Methode die richtige Wahl, um eine vorhandene Ressource teilweise zu aktualisieren, und PUT sollte nur verwendet werden, wenn Sie eine Ressource vollständig ersetzen.

Sie sollten es also folgendermaßen verwenden:

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"

RESTful-Praktiken weisen darauf hin, dass es keine Rolle spielen sollte, was Sie unter / {id} eingeben - der Inhalt des Datensatzes sollte auf den von der Nutzlast bereitgestellten aktualisiert werden -, aber GET / {id} sollte weiterhin mit derselben Ressource verknüpft sein.

Mit anderen Worten, PUT / 3 wird möglicherweise auf die Nutzlast-ID auf 4 aktualisiert, aber GET / 3 sollte weiterhin mit derselben Nutzlast verknüpft sein (und die mit der auf 4 eingestellte ID zurückgeben).

Wenn Sie entscheiden, dass Ihre API dieselbe Kennung im URI und in der Nutzlast benötigt, müssen Sie sicherstellen, dass sie übereinstimmt. Verwenden Sie jedoch auf jeden Fall PATCH anstelle von PUT, wenn Sie die ID in der Nutzlast ausschließen, die in ihrer Gesamtheit vorhanden sein soll . Hier hat die akzeptierte Antwort etwas falsch gemacht. PUT muss die gesamte Ressource ersetzen, wobei der Patch teilweise sein kann.

CompEng88
quelle
1

Es ist nichts Schlechtes, unterschiedliche Ansätze zu verwenden. aber ich denke der beste weg ist die lösung mit 2 ..

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

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

Es wird meistens auf diese Weise verwendet, selbst wenn das Entitätsframework diese Technik verwendet, wenn die Entität in dbContext hinzugefügt wird. Die Klasse ohne die generierte ID ist eine ID, die durch Referenz in Entity Framework generiert wird.

Shan Khan
quelle
1

Ich betrachte dies aus der Sicht von JSON-LD / Semantic Web, da dies ein guter Weg ist, um eine echte REST-Konformität zu erreichen, wie ich in diesen Folien beschrieben habe . Aus dieser Perspektive ist es keine Frage, Option (1) zu wählen, da die ID (IRI) einer Webressource immer der URL entsprechen sollte, über die ich die Ressource nachschlagen / dereferenzieren kann. Ich denke, die Überprüfung ist weder wirklich schwer zu implementieren noch rechenintensiv. Daher halte ich dies nicht für einen triftigen Grund, Option (2.) zu wählen. Ich denke, Option (3.) ist nicht wirklich eine Option, da POST (neu erstellen) eine andere Semantik hat als PUT (aktualisieren / ersetzen).

Vanthome
quelle
0

Möglicherweise müssen Sie sich die PATCH / PUT-Anforderungstypen ansehen.

PATCH-Anforderungen werden verwendet, um eine Ressource teilweise zu aktualisieren, während Sie bei PUT-Anforderungen die gesamte Ressource dort senden müssen, wo sie auf dem Server überschrieben wird.

Ich denke, Sie sollten immer eine ID in der URL haben, da dies eine Standardpraxis ist, um eine Ressource zu identifizieren. Sogar die Stripe-API funktioniert so.

Sie können eine PATCH-Anforderung verwenden, um eine Ressource auf dem Server mit der ID zu aktualisieren, um sie zu identifizieren, aber die tatsächliche ID nicht aktualisieren.

Noman Ur Rehman
quelle