web-dev-qa-db-ja.com

RESTfulAPIと一括操作

共有データベースでCRUD操作を実行する中間層があります。製品を.NETCoreに変換したとき、APIにRESTを使用することも検討したいと思いました。これは、CRUDがうまく機能するはずだからです。RESTは単一レコード操作に最適なソリューションですが、たとえば1,000レコードを削除したい場合はどうなりますか?

すべてのプロフェッショナルマルチユーザーアプリケーションには、楽観的同時実行性チェックの概念があります。フィードバックがなければ、あるユーザーに別のユーザーの作業を一掃させることはできません。私が理解しているように、RESTはHTTPETagヘッダーレコードでこれを処理します。クライアントから送信されたETagがサーバーのタグと一致しない場合は、412前提条件が失敗しました。これまでのところ、とても良いです。しかし、1,000レコードを削除したい場合は、何を使用しますか?1,000回の個別呼び出しの往復時間はかなり長いので、REST関与するバッチ操作を処理する楽観的同時実行性

6
Quarkly

RESTは、リソースとクライアントのサーバーからの分離に重点を置いていますが、単純なCRUDアーキテクチャやプロトコルではありません。 CRUDとRESTは非常に似ているように見えますが、RESTの原則 多くの場合、副作用もある可能性があります を介してリソースを管理します。 REST単純なCRUDは単純化しすぎているため、.

RESTリソースのバッチ処理に関して、基盤となるプロトコル(ほとんどの場合HTTP)は、使用できる機能を定義します。HTTPは、複数のリソースを変更するために使用できるいくつかの操作を定義します。 。

POSTは、プロトコルの万能のスイスアーミーナイフであり、文字通り好みに合わせてリソースを管理するために使用できます。セマンティクスは開発者によって定義されているため、これを使用して、複数のリソースを一度に作成、更新、または削除できます。

PUTには、特定のURIで取得可能なリソースの状態をリクエストのペイロード本体に置き換えるセマンティクスがあります。 PUTリクエストを「list」リソースに送信し、ペイロードがエントリのリストを定義している場合は、バッチ操作も実行できます。

POSTメソッドとPUTメソッドの基本的な違いは、囲まれた表現の意図が異なることで強調されています。POSTリクエストのターゲットリソースは、リソース自体のセマンティクスに従った囲まれた表現。一方、PUTリクエストの囲まれた表現は、ターゲットリソースの状態を置き換えるものとして定義されます。

.。

ターゲットリソースに適用されたPUT要求は、他のリソースに副作用をもたらす可能性があります。たとえば、記事には、特定の各バージョンを識別するURI(ある時点で現在のバージョンのリソースと同じ状態を共有する異なるリソース)とは別の「現在のバージョン」(リソース)を識別するためのURIがある場合があります。したがって、「現在のバージョン」のURIでPUT要求が成功すると、ターゲットリソースの状態の変更に加えて、新しいバージョンのリソースが作成され、関連するリソース間にリンクが追加される可能性があります。 ( ソース

PATCHRFC 5789 )はまだHTTPプロトコルに含まれていませんが、多くのフレームワークでサポートされています。これは主に、複数のリソースを一度に変更したり、リソースの部分的な更新を実行したりするために使用されます。更新された部分が他のリソースのサブリソースである場合は、PUTも実行できます。その場合、外部リソースを部分的に更新する効果があります。

PATCHリクエストには、リソースを目的の状態に変換するためにサーバーが実行する必要のある手順が含まれていることを知っておくことが重要です。したがって、クライアントは現在の状態を取得し、変換に必要な手順を事前に計算する必要があります。このトピックに関する非常に有益なブログ投稿は ばかみたいにパッチを当てないでください です。ここで JSONパッチ[〜#〜] rfc [〜#〜] )は、PATCHの概念を明確に視覚化するJSONベースのメディアタイプです。パッチ要求は、完全に適用するか(パッチ要求で定義された各操作)、まったく適用しない必要があります。したがって、トランザクションスコープの処理と、いずれかの操作が失敗した場合のロールバックが必要です。

ETagヘッダーやIfModifiedSinceヘッダーなどの条件付きリクエストは、 RFC 7232 で定義されており、HTTPリクエストで使用して、リクエストが最新のものに適用された場合にのみ変更を実行できます。リソースのバージョンであるため、(分散)データベースの楽観的ロックと相関関係があります。

ここまでは順調ですね。しかし、1,000レコードを削除したい場合はどうすればよいですか?

これは、使用するフレームワークによって異なります。 PATCHをサポートしている場合、私は明らかにPATCHに投票します。そうでない場合は、セマンティクスが明確に定義されているため、POSTが持つ非常に制限されたセマンティクスの時点でPUTよりもPUTを使用する方がおそらく安全です。バッチ削除の場合、PUTは、コレクション内のアイテムを削除してコレクション全体をクリアする結果となる空の本文でコレクションリソースをターゲットにすることによっても使用できます。ただし、一部のアイテムをコレクションに残す必要がある場合は、PATCHまたはPOSTの方がおそらく使いやすいでしょう。

5
Roman Vottner

私が正しく理解していれば、レコードごとに個別に楽観的同時実行性が必要です。つまり、各レコードは、その状態がクライアントの期待と一致する場合にのみ削除されます。 (コレクション全体の状態のみをアサートする場合は、If-Matchと412で十分です。)

Roman Vottnerの回答は、関連するHTTPメソッドを説明するのに優れていますが、いくつかの詳細を入力しようと思います。

買い手責任負担

これまたはそれを「どのようにREST処理するか」」について話すとき、技術的には、HTTPを任意の操作のトランスポートとして使用できることを理解しています。

したがって、RESTについて質問するときは、 統一されたインターフェイス —さまざまなクライアントやサーバーで理論的に使用できるアプローチに関心があると思います。

しかし、そこにあるキーワードは「理論的に」です。たとえば、独自のメディアタイプ(独自のJSON構造)を定義すると、クライアントは特定のAPIに対してコーディングする必要があるため、多くの均一性が失われます。その時点で、ジャンプするように依頼できます。あなたが望むどんなフープを通して。

ただし、可能な限り均一性を維持することに関心がある場合は、読み進めてください。

全部かゼロか

個々の前提条件のいずれかが失敗すると完全に失敗するオールオアナッシング操作が必要な場合は、Romanが示唆しているように、 [〜#〜] patch [〜#〜] を使用できます。 JSONパッチ 形式。このためには、パッチが適用される単一のJSONオブジェクトとしてのコレクションの概念的な表現が必要です。

たとえば、/my/collection/1/my/collection/4などのリソースがあるとします。 /my/collection/を次のように表すことができます。

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

ここで、"1""4"は、/my/collection/に関連するURLです。代わりにドメイン固有のIDを使用することもできますが、適切なRESTは不透明なURLに関して機能します。

標準では、実際にGET /my/collection/でこの表現を提供する必要はありませんが、そのような要求をサポートする場合は、その表現を使用する必要があります。とにかく、この構造に次のJSONパッチを適用できます。

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"},
    ...
]

ここで、pathはURLパスではなく、上記の表現への JSONポインター です。

すべてのパッチ操作が成功すると、 204(コンテンツなし) または 200(OK) のような成功ステータスコードで応答します。

ETag test操作のいずれかが失敗した場合は、 409(Conflict) で応答します。この場合、リクエスト自体に前提条件(If-Matchなど)がないため、 412(前提条件が失敗しました) で応答しないでください。

他に問題が発生した場合は、他の適切なステータスコードで応答します。 RFC5789§2.2 および RFC7231§6.6 を参照してください。

混合結果

「オール・オア・ナッシング」のセマンティクスが必要ない場合、私は標準化されたソリューションを知りません。 Romanが指摘しているように、この場合はPATCHメソッドを使用できませんが、カスタムメディアタイプでPOSTを使用できます( RFC6838§3.4 )。このような:

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\""},
        ...
    ]
}

個々の削除が成功したかどうかに関係なく、このような要求には200(OK)で応答できます。別のオプションは 207(Multi-Status) ですが、この場合はメリットがなく、WebDAV以外では広く使用されていないため、 Postelの法則 =そこに行かないことを提案します。

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

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

もちろん、パッチがそもそも無効だった場合は、代わりに 415(サポートされていないメディアタイプ) または 422(処理不可能なエンティティ) で適切に応答する必要があります。

別の角度

1,000回の個別通話の往復時間はかなりのものです

HTTP /1.1にあります。ただし、HTTP/2を使用できる場合(同時リクエストのサポートがはるかに優れており、リクエストあたりのネットワークオーバーヘッドがはるかに小さい)、1000個の個別のリクエストで問題なく動作する可能性があります。

0
Vasiliy Faronov