wake-up-neo.com

RESTful API- und Bulk-Operationen

Ich habe eine mittlere Schicht, die CRUD-Operationen für eine gemeinsam genutzte Datenbank ausführt. Als ich das Produkt auf .NET Core konvertierte, dachte ich, ich würde auch die Verwendung von REST für die API in Betracht ziehen, da CRUD das sein soll, was es gut kann. Es scheint, als wäre REST eine großartige Lösung für einzelne Datensatzoperationen, aber was passiert, wenn ich beispielsweise 1.000 Datensätze löschen möchte?

In jeder professionellen Mehrbenutzeranwendung gibt es ein Konzept für die Prüfung der optimistischen Parallelität: Sie können nicht zulassen, dass ein Benutzer die Arbeit eines anderen Benutzers ohne Feedback löscht. Soweit ich weiß, behandelt REST dies mit dem HTTP-ETag-Header-Datensatz. Wenn das vom Client gesendete ETag nicht mit dem Tag des Servers übereinstimmt, geben Sie 412 Vorbedingung fehlgeschlagen aus. So weit, ist es gut. Aber was verwende ich, wenn ich 1.000 Datensätze löschen möchte? Die Hin- und Her-Zeit für 1.000 einzelne Aufrufe ist beträchtlich. Wie würde REST eine Batch-Operation handhaben, die Optimistic Concurrency umfasst?

5
Donald Airey

Der Schwerpunkt von REST liegt auf Ressourcen und der Entkopplung von Clients von Servern. Dies ist jedoch keine einfache CRUD-Architektur oder ein einfaches CRUD-Protokoll. Während CRUD und REST sehr ähnlich zu sein scheinen, kann das Verwalten von Ressourcen durch REST Prinzipien oft auch Nebenwirkungen haben . Daher ist die Beschreibung von REST als einfache CRUD-Sache eine zu starke Vereinfachung.

In Bezug auf die Stapelverarbeitung von REST Ressourcen definiert das zugrunde liegende Protokoll (meistens HTTP) die Funktionen, die verwendet werden können. HTTP definiert eine Reihe von Vorgängen, mit denen mehrere Ressourcen geändert werden können.

POST ist das universelle Schweizer Taschenmesser des Protokolls und kann verwendet werden, um Ressourcen buchstäblich nach Ihren Wünschen zu verwalten. Da die Semantik vom Entwickler definiert wird, können Sie damit mehrere Ressourcen gleichzeitig erstellen, aktualisieren oder löschen.

PUT hat die Semantik, den Status einer Ressource, die bei einem bestimmten URI erhältlich ist, durch den Nutzlastkörper der Anforderung zu ersetzen. Wenn Sie eine PUT-Anfrage an eine "Listen" -Ressource senden und die Payload eine Liste von Einträgen definiert, können Sie auch eine Batch-Operation ausführen.

Der grundlegende Unterschied zwischen den Methoden POST und PUT wird durch die unterschiedliche Absicht für die beigefügte Darstellung hervorgehoben. Die Zielressource in einer POST -Anforderung soll die eingeschlossene Darstellung gemäß der eigenen Semantik der Ressource behandeln, während die eingeschlossene Darstellung in einer PUT-Anforderung so definiert ist, dass sie den Status der Zielressource ersetzt.

...

Eine auf die Zielressource angewendete PUT-Anforderung kann Nebenwirkungen auf andere Ressourcen haben. Beispielsweise kann ein Artikel einen URI zur Identifizierung der "aktuellen Version" (einer Ressource) haben, der von den URIs zur Identifizierung der einzelnen Versionen getrennt ist (verschiedene Ressourcen, die zu einem bestimmten Zeitpunkt denselben Status wie die Ressource der aktuellen Version hatten). Bei einer erfolgreichen PUT-Anforderung für den URI "Aktuelle Version" wird daher möglicherweise zusätzlich zum Ändern des Status der Zielressource eine neue Versionsressource erstellt. Außerdem werden möglicherweise Verknüpfungen zwischen den zugehörigen Ressourcen hinzugefügt. ( Quelle )

PATCH ( RFC 5789 ) ist noch nicht im HTTP-Protokoll enthalten, wird jedoch von zahlreichen Frameworks unterstützt. Es wird hauptsächlich zum gleichzeitigen Ändern mehrerer Ressourcen oder zum teilweisen Aktualisieren von Ressourcen verwendet. Dies kann PUT auch erreichen, wenn der aktualisierte Teil eine Subressource einer anderen Ressource ist. In diesem Fall wird die äußere Ressource teilweise aktualisiert.

Es ist wichtig zu wissen, dass eine PATCH Anfrage die notwendigen Schritte enthält, die ein Server ausführen muss, um eine Ressource in den beabsichtigten Zustand zu versetzen. Ein Client muss daher vorab den aktuellen Status abrufen und die für die Transformation erforderlichen Schritte berechnen. Ein sehr informativer Blogeintrag zu diesem Thema ist Nicht wie ein Idiot patchen . Hier ist JSON Patch ( RFC ) ein auf JSON basierender Medientyp, der das PATCH-Konzept klar visualisiert. Eine Patch-Anforderung muss entweder vollständig (jede in der Patch-Anforderung definierte Operation) oder überhaupt nicht angewendet werden. Daher ist eine transaktionsbezogene Behandlung und ein Rollback erforderlich, falls eine der Operationen fehlschlägt.

Bedingte Anforderungen wie ETag- und IfModifiedSince -Header sind in RFC 7232 definiert und können in HTTP-Anforderungen nur verwendet werden, um die Änderungen durchzuführen, wenn die Anforderung auf die neueste Version der Ressource angewendet wird und daher einer optimistischen Sperrung (verteilt) entspricht ) Datenbanken.

So weit, ist es gut. Aber was verwende ich, wenn ich 1.000 Datensätze löschen möchte?

Dies hängt davon ab, welches Framework Sie verwenden. Wenn es PATCH unterstützt, stimme ich eindeutig für PATCH. Falls dies nicht der Fall ist, ist es wahrscheinlich sicherer, POST als PUT zu verwenden, da die sehr restriktive Semantik PUT vorliegt, da die Semantik dann von Ihnen klar definiert ist. Im Falle eines Batch-Löschvorgangs kann PUT auch verwendet werden, indem auf die Auflistungsressource mit einem leeren Körper abgezielt wird, wodurch alle Elemente in der Auflistung entfernt und daher die gesamte Auflistung gelöscht werden. Wenn jedoch einige der Elemente in der Sammlung verbleiben sollen, sind PATCH oder POST wahrscheinlich einfacher zu verwenden.

4
Roman Vottner

Wenn ich das richtig verstehe, möchten Sie optimistische Parallelität für jeden Datensatz einzeln. Das heißt, jeder Datensatz darf nur gelöscht werden, wenn sein Status den Erwartungen des Kunden entspricht. (Wenn Sie nur den Status der gesamten Sammlung bestätigen möchten, reichen If-Match und 412 aus.)

Die Antwort von Roman Vottner eignet sich hervorragend zur Erläuterung der beteiligten HTTP-Methoden, aber ich werde versuchen, einige Details einzufügen.

Vorbehalt Emptor

Wenn wir darüber sprechen, wieRESTmit diesem oder jenem umgehen würde, verstehen Sie, dass Sie HTTP technisch als Transport für jede Operation auf eine Weise verwenden können, die zu Ihnen passt.

Wenn Sie also nach REST fragen, gehe ich davon aus, dass Sie an einer einheitlichen Schnittstelle - interessiert sind, die theoretisch von einer Reihe verschiedener Clients und Server verwendet werden kann.

Aber das Schlüsselwort ist dort "theoretisch". Wenn Sie beispielsweise Ihren eigenen Medientyp (Ihre eigene JSON-Struktur) definiert haben, geht ein Großteil der Einheitlichkeit verloren, da ein Client ohnehin anhand Ihrer spezifischen API codiert werden müsste und Sie ihn an diesem Punkt zum Wechseln auffordern können durch alle Reifen, die Sie wollen.

Aber wenn Sie immer noch daran interessiert sind, so viel wie möglich von der Einheitlichkeit zu retten, lesen Sie weiter.

Alles oder nichts

Wenn Sie eine Alles-oder-Nichts-Operation wünschen, die vollständig fehlschlägt, wenn eine der einzelnen Voraussetzungen nicht erfüllt ist, können Sie, wie Roman vorschlägt, PATCH mit dem Format JSON Patch verwenden. Dazu benötigen Sie eine konzeptionelle Darstellung Ihrer Sammlung als einzelnes JSON-Objekt, auf das der Patch angewendet werden soll.

Angenommen, Sie haben Ressourcen wie /my/collection/1, /my/collection/4 usw. Sie könnten /my/collection/ folgendermaßen darstellen:

{
    "resources": {
        "1": {
            "href": "1",
            "etag": "\"BRkDVtYw\"",
            "name": "Foo Bar",
            "price": 1234.5,
            ...
        },
        "4": {
            "href": "4",
            "etag": "\"RCi8knuN\"",
            "name": "Baz Qux",
            "price": 2345.6,
            ...
        },
        ...
    }
}

Hier sind "1" und "4" URLs relativ zu /my/collection/. Sie könnten stattdessen domänenspezifische IDs verwenden, aber das richtige REST funktioniert in Bezug auf undurchsichtige URLs.

Die Standards verlangen nicht, dass Sie diese Darstellung auf GET /my/collection/ tatsächlich bereitstellen. Wenn Sie jedoch eine solche Anforderung unterstützen, sollten Sie diese Darstellung verwenden. Auf diese Struktur können Sie jedoch den folgenden JSON-Patch anwenden:

PATCH /my/collection/ HTTP/1.1
Content-Type: application/json-patch+json

[
    {"op": "test", "path": "/resources/1/etag", "value": "\"BRkDVtYw\""},
    {"op": "remove", "path": "/resources/1"},
    {"op": "test", "path": "/resources/4/etag", "value": "\"RCi8knuN\""},
    {"op": "remove", "path": "/resources/4"},
    ...
]

Hier ist path kein URL-Pfad, sondern ein JSON-Zeiger in der obigen Darstellung.

Wenn alle Patch-Vorgänge erfolgreich waren, antworten Sie mit einem erfolgreichen Statuscode wie 204 (No Content) oder 200 (OK) .

Wenn eine der ETag test -Operationen fehlschlägt, antworten Sie mit 409 (Konflikt) . Sie sollten in diesem Fall nicht mit 412 (Vorbedingung fehlgeschlagen) antworten, da die Anforderung selbst keine Vorbedingung (wie If-Match) enthält.

Wenn etwas anderes schief geht, antworten Sie mit anderen geeigneten Statuscodes: siehe RFC 5789 § 2.2 und RFC 7231 § 6.6 .

Gemischtes Ergebnis

Wenn Sie keine "Alles-oder-Nichts" -Semantik wünschen, sind mir keine standardisierten Lösungen bekannt. In diesem Fall können Sie die PATCH-Methode nicht verwenden, sondern POST mit einem benutzerdefinierten Medientyp ( RFC 6838 § 3.4 ). Es könnte so aussehen:

POST /my/collection/ HTTP/1.1
Content-Type: application/x.my-patch+json
Accept: application/x.my-patch-results+json

{
    "delete": [
        {"href": "1", "if-match": "\"BRkDVtYw\""},
        {"href": "4", "if-match": "\"RCi8knuN\""},
        ...
    ]
}

Sie können auf eine solche Anfrage mit 200 (OK) antworten, unabhängig davon, ob eine der einzelnen Löschvorgänge erfolgreich war. Eine andere Option wäre 207 (Multi-Status) , aber ich sehe in diesem Fall keine Vorteile, und es wird außerhalb von WebDAV nicht häufig verwendet, also Postels Gesetz würde vorschlagen, nicht dorthin zu gehen .

HTTP/1.1 200 OK
Content-Type: application/x.my-patch-results+json

{
    "delete": [
        {"href": "1", "success": true},
        {"href": "4", "success": false, "error": {...}},
        ...
    ]
}

Wenn der Patch an erster Stelle ungültig war, sollten Sie stattdessen entsprechend mit 415 (Nicht unterstützter Medientyp) oder 422 (Nicht verarbeitbare Entität) antworten.

Ein anderer Winkel

Die Hin- und Her-Zeit für 1.000 Einzelgespräche ist beträchtlich

Es ist in HTTP/1.1. Wenn Sie jedoch HTTP/2 verwenden können, das die gleichzeitigen Anforderungen viel besser unterstützt, und der Netzwerkaufwand pro Anforderung viel geringer ist, funktionieren 1000 einzelne Anforderungen möglicherweise einwandfrei.

0
Vasiliy Faronov