REST-API - Massenerstellung oder -aktualisierung in einer einzelnen Anforderung [geschlossen]

91

Nehmen wir an, es gibt zwei Ressourcen Binderund eine DocAssoziationsbeziehung bedeutet, dass die Docund für sich Binderstehen. Dockönnte oder könnte nicht gehören Binderund Binderkönnte leer sein.

Wenn ich eine REST-API entwerfen möchte, mit der ein Benutzer eine Sammlung von Docs senden kann , IN EINER EINZIGEN ANFRAGE , wie folgt:

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

Und für jedes Dokument in der docs,

  • Wenn das docexistiert, weisen Sie es zuBinder
  • Wenn das docnicht vorhanden ist, erstellen Sie es und weisen Sie es dann zu

Ich bin wirklich verwirrt, wie dies umgesetzt werden soll:

  • Welche HTTP-Methode soll verwendet werden?
  • Welcher Antwortcode muss zurückgegeben werden?
  • Ist das überhaupt für REST qualifiziert?
  • Wie würde die URI aussehen? /binders/docs?
  • Was ist, wenn einige Elemente einen Fehler auslösen, die anderen jedoch durchlaufen? Welcher Antwortcode muss zurückgegeben werden? Sollte die Massenoperation atomar sein?
Norbert
quelle

Antworten:

58

Ich denke, dass Sie eine POST- oder PATCH-Methode verwenden könnten, um dies zu handhaben, da sie normalerweise dafür ausgelegt sind.

  • Die Verwendung einer POSTMethode wird normalerweise verwendet, um ein Element hinzuzufügen, wenn es für Listenressourcen verwendet wird. Sie können jedoch auch mehrere Aktionen für diese Methode unterstützen. Siehe diese Antwort: So aktualisieren Sie eine REST-Ressourcensammlung . Sie können auch verschiedene Darstellungsformate für die Eingabe unterstützen (wenn sie einem Array oder einem einzelnen Element entsprechen).

    In diesem Fall ist es nicht erforderlich, Ihr Format zu definieren, um das Update zu beschreiben.

  • Die Verwendung einer PATCHMethode ist ebenfalls geeignet, da entsprechende Anforderungen einer teilweisen Aktualisierung entsprechen. Laut RFC5789 ( http://tools.ietf.org/html/rfc5789 ):

    Einige Anwendungen, die das Hypertext Transfer Protocol (HTTP) erweitern, erfordern eine Funktion, um teilweise Ressourcenänderungen vorzunehmen. Die vorhandene HTTP-PUT-Methode ermöglicht nur das vollständige Ersetzen eines Dokuments. Dieser Vorschlag fügt eine neue HTTP-Methode, PATCH, hinzu, um eine vorhandene HTTP-Ressource zu ändern.

    In diesem Fall müssen Sie Ihr Format definieren, um die teilweise Aktualisierung zu beschreiben.

Ich denke, dass in diesem Fall POSTund PATCHsind ziemlich ähnlich, da Sie nicht wirklich die Operation beschreiben müssen, die für jedes Element zu tun ist. Ich würde sagen, dass es vom Format der zu sendenden Darstellung abhängt.

Der Fall von PUTist etwas weniger klar. Wenn Sie eine Methode verwenden PUT, sollten Sie die gesamte Liste bereitstellen. Tatsächlich ersetzt die in der Anforderung bereitgestellte Darstellung die Listenressource 1.

Sie können zwei Optionen bezüglich der Ressourcenpfade haben.

  • Verwenden des Ressourcenpfads für die Dokumentliste

In diesem Fall müssen Sie den Link von Dokumenten mit einem Ordner in der Darstellung, die Sie in der Anforderung angeben, explizit angeben.

Hier ist eine Beispielroute dafür /docs.

Der Inhalt eines solchen Ansatzes könnte für die Methode sein 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) },
    (...)
]
  • Verwenden des Unterressourcenpfads des Binderelements

Darüber hinaus können Sie auch Unterrouten nutzen, um die Verbindung zwischen Dokumenten und Ordnern zu beschreiben. Die Hinweise zur Zuordnung zwischen einem Dokument und einem Ordner müssen jetzt nicht mehr im Anforderungsinhalt angegeben werden.

Hier ist eine Beispielroute dafür /binder/{binderId}/docs. In diesem Fall senden Sie eine Liste von Dokumenten mit einer Methode POSToder PATCHhängen Dokumente an den Ordner mit der Kennung binderIdan, nachdem Sie das Dokument erstellt haben, falls es nicht vorhanden ist.

Der Inhalt eines solchen Ansatzes könnte für die Methode sein 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) },
    (...)
]

In Bezug auf die Antwort liegt es an Ihnen, die Antwortstufe und die zurückzugebenden Fehler zu definieren. Ich sehe zwei Ebenen: die Statusebene (globale Ebene) und die Nutzlaststufe (dünnere Ebene). Sie müssen auch festlegen, ob alle Ihrer Anfrage entsprechenden Einfügungen / Aktualisierungen atomar sein müssen oder nicht.

  • Atomic

In diesem Fall können Sie den HTTP-Status nutzen. Wenn alles gut geht, bekommen Sie einen Status 200. Wenn nicht, ein anderer Status, z. B. 400wenn die angegebenen Daten nicht korrekt sind (z. B. die Binder-ID ist nicht gültig) oder etwas anderes.

  • Nicht atomar

In diesem Fall wird ein Status 200zurückgegeben, und es liegt an der Antwortdarstellung, zu beschreiben, was getan wurde und wo letztendlich Fehler auftreten. ElasticSearch hat in seiner REST-API einen Endpunkt für die Massenaktualisierung. Dies könnte Ihnen einige Ideen auf dieser Ebene geben: http://www.elasticsearch.org/guide/en/elasticsearch/guide/current/bulk.html .

  • Asynchron

Sie können auch eine asynchrone Verarbeitung implementieren, um die bereitgestellten Daten zu verarbeiten. In diesem Fall wird der HTTP-Status zurückgegeben 202. Der Client muss eine zusätzliche Ressource abrufen, um zu sehen, was passiert.

Bevor ich fertig bin, möchte ich auch darauf hinweisen, dass die OData-Spezifikation das Problem in Bezug auf die Beziehungen zwischen Entitäten mit der Funktion " Navigationslinks" behebt . Vielleicht könntest du dir das mal ansehen ;-)

Der folgende Link kann Ihnen auch helfen: https://templth.wordpress.com/2014/12/15/designing-a-web-api/ .

Hoffe es hilft dir, Thierry

Thierry Templier
quelle
Ich habe auf Frage gefolgt. Ich habe mich für flache Routen ohne verschachtelte Unterressource entschieden. Um alle Dokumente GET /docsabzurufen, rufe ich alle Dokumente in einem bestimmten Ordner auf und rufe sie ab GET /docs?binder_id=x. Um eine Teilmenge der Ressourcen zu löschen, würde ich anrufen DELETE /docs?binder_id=xoder sollte ich DELETE /docsmit einem {"binder_id": x}im Anfragetext anrufen ? Würden Sie jemals PATCH /docs?binder_id=xfür ein Batch-Update verwenden oder nur PATCH /docsPaare übergeben?
Andy Fusniak
34

Sie müssen wahrscheinlich POST oder PATCH verwenden, da es unwahrscheinlich ist, dass eine einzelne Anforderung, die mehrere Ressourcen aktualisiert und erstellt, idempotent ist.

Tun PATCH /docsist definitiv eine gültige Option. Die Verwendung der Standard-Patch-Formate ist für Ihr bestimmtes Szenario möglicherweise schwierig. Ich bin mir nicht sicher.

Sie könnten 200 verwenden. Sie könnten auch 207 - Multi Status verwenden

Dies kann auf REST-artige Weise erfolgen. Meiner Meinung nach besteht der Schlüssel darin, über eine Ressource zu verfügen, die eine Reihe von Dokumenten zum Aktualisieren / Erstellen akzeptiert.

Wenn Sie die PATCH-Methode verwenden, würde ich denken, dass Ihre Operation atomar sein sollte. dh ich würde den 207-Statuscode nicht verwenden und dann Erfolge und Fehler im Antworttext melden. Wenn Sie die POST-Operation verwenden, ist der 207-Ansatz sinnvoll. Sie müssen Ihren eigenen Antworttext entwerfen, um zu kommunizieren, welche Vorgänge erfolgreich waren und welche fehlgeschlagen sind. Mir ist kein standardisierter bekannt.

Darrel Miller
quelle
Ich danke dir sehr. Mit This can be done in a RESTful waymeinen Sie, dass das Aktualisieren und Erstellen separat durchgeführt werden muss?
Norbert
1
@norbertpy Das Ausführen einer Schreiboperation für eine Ressource kann dazu führen, dass andere Ressourcen aus einer einzigen Anforderung aktualisiert und erstellt werden. REST hat damit kein Problem. Meine Wahl der Phrase war, weil einige Frameworks Massenoperationen implementieren, indem sie HTTP-Anforderungen in mehrteilige Dokumente serialisieren und dann die serialisierten HTTP-Anforderungen als Stapel senden. Ich denke, dass dieser Ansatz die REST-Einschränkung für die Ressourcenidentifizierung verletzt.
Darrel Miller
19

PUT ing

PUT /binders/{id}/docs Erstellen oder aktualisieren Sie ein einzelnes Dokument und verknüpfen Sie es mit einem Ordner

z.B:

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

PATCH ing

PATCH /docs Erstellen Sie Dokumente, wenn sie nicht vorhanden sind, und verknüpfen Sie sie mit Ordnern

z.B:

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

Ich werde später weitere Einblicke geben, aber in der Zwischenzeit, wenn Sie möchten, werfen Sie einen Blick auf RFC 5789 , RFC 6902 und William Durands Please. Patch nicht wie ein Idiot- Blogeintrag.

Mauricio Morales
quelle
2
Manchmal benötigt der Client einen Massenbetrieb und es ist ihm egal, ob die Ressource vorhanden ist oder nicht. Wie ich in der Frage sagte, möchte der Kunde ein paar senden docsund sie damit verknüpfen binders. Der Client möchte Ordner erstellen, wenn diese nicht vorhanden sind, und die Zuordnung vornehmen, wenn dies der Fall ist. In ONE SINGLE BULK Anfrage.
Norbert
12

In einem Projekt, an dem ich gearbeitet habe, haben wir dieses Problem gelöst, indem wir etwas implementiert haben, das wir "Stapel" -Anfragen genannt haben. Wir haben einen Pfad definiert, /batchin dem wir json im folgenden Format akzeptiert haben:

[  
   {
      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
      }
   },
]

Die Antwort hat den Statuscode 207 (Multi-Status) und sieht folgendermaßen aus:

[  
   {
      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
   },
]

Sie können in dieser Struktur auch Unterstützung für Header hinzufügen. Wir haben etwas implementiert, das sich als nützlich erwiesen hat: Variablen, die zwischen Anforderungen in einem Stapel verwendet werden sollen. Dies bedeutet, dass wir die Antwort von einer Anforderung als Eingabe für eine andere verwenden können.

Facebook und Google haben ähnliche Implementierungen:
https://developers.google.com/gmail/api/guides/batch
https://developers.facebook.com/docs/graph-api/making-multiple-requests

Wenn Sie eine Ressource mit demselben Aufruf erstellen oder aktualisieren möchten, würde ich je nach Fall entweder POST oder PUT verwenden. Wenn das Dokument bereits vorhanden ist, soll das gesamte Dokument wie folgt sein:

  1. Ersetzt durch das Dokument, das Sie einschicken (dh fehlende Eigenschaften in der Anfrage werden entfernt und bereits vorhandene überschrieben)?
  2. Mit dem von Ihnen gesendeten Dokument zusammengeführt (dh fehlende Eigenschaften auf Anfrage werden nicht entfernt und bereits vorhandene Eigenschaften werden überschrieben)?

Wenn Sie das Verhalten von Alternative 1 möchten, sollten Sie einen POST verwenden, und wenn Sie das Verhalten von Alternative 2 möchten, sollten Sie PUT verwenden.

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

Wie die Leute bereits vorgeschlagen haben, könnten Sie sich auch für PATCH entscheiden, aber ich bevorzuge es, die APIs einfach zu halten und keine zusätzlichen Verben zu verwenden, wenn sie nicht benötigt werden.

David Berg
quelle
5
Gefällt mir diese Antwort für den Proof-of-Concept sowie die Google- und Facebook-Links. Aber nicht einverstanden mit dem letzten Teil über POST oder PUT. In den beiden genannten Fällen sollte die erste Antwort PUT und die zweite PATCH sein.
RayLuo
@ RayLuo, kannst du erklären, warum wir zusätzlich zu POST und PUT PATCH benötigen?
David Berg
2
Denn dafür wurde der PATCH erfunden. Sie können diese Definition lesen und sehen, wie PUT und PATCH mit Ihren 2 Aufzählungspunkten übereinstimmen.
RayLuo
@DavidBerg, Es scheint, dass Google einen anderen Ansatz für die Verarbeitung von Stapelanforderungen bevorzugt hat, dh den Header und den Text jeder Unteranforderung mit einer Begrenzung wie dem entsprechenden Teil einer Hauptanforderung zu trennen --batch_xxxx. Gibt es einige entscheidende Unterschiede zwischen den Lösungen von Google und Facebook? Darüber hinaus klingt es sehr interessant, die Antwort von einer Anfrage als Eingabe für eine andere zu verwenden. Würde es Ihnen etwas ausmachen, weitere Details mitzuteilen? oder welche Art von Szenario sollte verwendet werden?
Yang