web-dev-qa-db-ja.com

REST APIを「正しい」方法で実行しないことの結果?

私はこの方法でこの質問をします-私のREST APIを「正しい」方法で実装しないことのソフトウェアエンジニアリングの懸念は何ですか?

「正しい」方法とはどういう意味ですか?さて、正しい方法についての私の認識を説明させてください。それから、私がそれをどのように行っているかをお話しします私はJSON REST API)について話している。

正しい方法

  1. 無国籍。これは私が得る部分です。クライアントは常に100%いつまでも状態を維持します。それはサーバーの仕事ではなく、クライアントの仕事です。

  2. 各動詞の予想されるアクションと応答:

    • [〜#〜] get [〜#〜]-完全に指定されたリソースを取得します。これは、リクエスト内の承認またはクエリパラメータによってのみ制限されます。これにより、プロセス内のリソースが変更されないことが保証されます
    • [〜#〜] post [〜#〜]-リソース記述全体(JSONオブジェクトなど)を指定してリソースを作成し、そのリソースを返します。日付またはID。
    • [〜#〜] delete [〜#〜]-指定されたリソースを削除し、ある種の200 OKのみを応答として返します
    • [〜#〜] put [〜#〜]-オブジェクト宣言全体を入力として指定し、特定の場所でリソースを更新し、すべてのフィールドを更新します入力で与えられた各フィールドへのリソースの。明確にするために、これはentireオブジェクトが入力として渡されることを期待しています。更新されたリソース全体が、すべてのフィールドとともに(許可またはその他の入力フラグに従って)返されます。
    • [〜#〜] patch [〜#〜]-リソースの変更が必要なフィールドのみを指定して、入力として指定された指定されたリソースのフィールドのみを更新します。 (これは私が不明瞭なところです):リソース全体が返されますか? (または、更新されたフィールドだけですか?Dunno。気にしないでください。)
  3. リソースパス。リソースの相互関係を考えると、リソースパスは次のいずれかになります。
    • / parentresource /:id
    • / parentresource /:id/childresource
    • / parentresource /:id/childresource /:childId
    • / parentresource /:id/childresource /:childId/subresource /:subresourceId(この例では、サブリソースは子リソースに属し、子リソースは親リソースに属しています)。

やりたいこと

上記は、REST APIがどのように機能することになっているのかについての私の理解です。次に、上記のバリエーションのいくつかをリストしましょう。

  1. PUT/PATCH-変更のためにリソース全体を渡す意味は何ですか?リソースの変更にはPUTのみを使用し、更新するフィールドのみを渡します。その結果、パッチを使用する必要はありません
  2. リソースパス-アプリケーションでGUIDを使用しています。その結果、それらは世界的にユニークになります。単独でサブリソースを一意に参照できるのに、親リソースを含む完全なリソースパスが必要なのはなぜですか?お気に入り:

    /subresource /:subresourceId

    「正しい」方法で実行する場合、サブリソースを参照しようとすると、次のようなフルパスが必要になります。

    /parentresource /:id/childresource /:childId/subresource /:subresourceId

    それだけで十分ですか?私のパスに、指定された:childIdによって実際には所有されていない:subresourceIdと、親:idによって所有されていない:childIdのディットが含まれている場合、追加のエラー処理が必要になるためです。私のサーバー側は、リソースの承認を担当しています。フルパスではなく、リソース自体を参照することはできませんか?

  3. 戻り応答。たとえば、私のデータ構造が階層ツリーであり、ツリーの深さに実質的な制限がないとします。リソースは、ツリーのさまざまなレベルに階層的に配置されています。

    • GETは明白です。このツリー全体を取得した場合、リソース全体がリソース内に含まれた状態で、ツリー全体が応答として期待されます。
    • 私がPOSTで新しいリソースを作成する、PUTで​​更新する、またはDELETEで削除する場合、作成/更新/削除したリソースを表示するだけでなく、ツリーでデルタを表示したい。特にPOST、PUT、またはDELETEを実行するたびに、親ツリーのGETを再度呼び出す必要はありません。特に、ツリーにほとんど変更がなく、デルタのみを表示したい場合に使用します。

うまくいけば、私の質問は明確です。

私が説明したREST実装が表示されたとしたら、それを見て、ソフトウェアエンジニアリングの懸念事項について教えてください。もしそうなら、それは何でしょうか?

8
Michael Plautz

ここでの基本的な答えは、あなたのアイデアは技術レベルで機能する可能性があるということですが、それがRESTの標準化された規則に準拠していることを意味するわけではありません。


  1. PUT/PATCH-変更のためにリソース全体を渡す意味は何ですか?リソースの変更にはPUTのみを使用し、更新するフィールドのみを渡します。その結果、パッチを使用する必要はありません

あなたのアイデアは技術レベルで機能しますが、RESTが説明されている方法ではありません。動作しているコード(つまり、コンパイルエラーやランタイムエラーはありません)に関する議論は常に規約の問題。明確な技術的優位性があるとは限りません。


  1. リソースパス-アプリケーションでGUIDを使用しています。その結果、それらは世界的にユニークになります。単独でサブリソースを一意に参照できるのに、親リソースを含む完全なリソースパスが必要なのはなぜですか?

「子/親」エンティティを定義する方法には多くのニュアンスがあります。最も一般的には、1対多(親から子)の関係を指します。

ただし、RESTの場合、子を子にする理由の1つは、親を介してのみアクセスできるという期待があり、グローバルに一意の(外部的に知られている)識別子を持たないことです。
これは ドメイン駆動開発における集合体(およびそのルート) と同じ哲学(必ずしも同じ理由である必要はない)に従っていると思います。

DDDアグリゲートは、単一のユニットとして扱うことができるドメインオブジェクトのクラスターです。集合体は、そのコンポーネントオブジェクトの1つを集合体のルートにします。 アグリゲートの外からの参照は、アグリゲートルートにのみ行く必要があります。

あなたの場合、あなたが「親」と呼ぶものは、集約ルートとして機能します。外部の発信者のための単一の連絡先(必要な場合)。

これから、子供は実際には別の集合体であると結論付けることができます。それは事実かもしれませんが、私はその決定について警告を発したいと思います。特定のタイプのフィールドに基づいてアーキテクチャを構築するべきではありません。すべてのエンティティに対して常にグローバルに一意のIDを使用し続けるかどうかを知る方法はありません。それが変化した場合、何らかの理由で、あなたはあなたのRESTアーキテクチャの実行可能性を危うくするでしょう;あなたは子供がもはや一意に識別可能ではなく、したがって必要とする状況になるかもしれないので親を通じて参照されます。


  1. POSTで新しいリソースを作成する、PUTで​​更新する、またはDELETEで削除する場合、作成/更新/削除したリソースを表示するだけでなく、ツリーでデルタを表示したい。

デザインの操作順序に違反しています。 A REST APIは、特に消費者に依存しないことを目的としています。APIは、その消費者の1人の仕様に従って構築されるべきではありません。

「ツリー内のデルタを表示したい」と言うと、実際に言っているのは「使用中のアプリケーションはツリー内のデルタを見るだけでよい」ということです。しかし、REST apiにとってそれは重要ではありません。標準化されたアプローチを提供するだけです。

高度にカスタマイズ可能ツールが欠けていることが多い標準化されたアプローチの性質であり、代わりに最も一般的に使用されるツールを支持しています。


パスから逸脱できますか?まあ、それは技術レベルで動作します。しかし、それは純粋ではありませんREST。これは非常に状況に応じたものであり、オプションを比較検討する必要があります。

  • 多くのさまざまな消費者に対応することが期待されるAPIを作成している場合は、RESTにできる限り従うことをお勧めします。
  • 自分で開発したコンシューマーを1つだけ持つAPIを構築する場合。純粋なRESTにこだわる必要はありません。
  • 道から外れるということは、他の開発者がそれを理解できるように、どのように迷ったかを文書化する必要があることを意味します。純粋なRESTを使用する場合、ドキュメントを作成する必要はなく、他の開発者はカスタマイズされたアプローチを理解するために時間と労力を費やす必要はありません。
3
Flater

私のREST APIを「正しい」方法で実装しないことのソフトウェアエンジニアリングの懸念は何ですか?

主なものは、私の考えでは、特定のビジネスケースではなく、標準のみを知っている汎用コンポーネントに作業を委任できることです。

統一されたインターフェースを順守している場合は、他の関係者が自分のコンポーネントとうまく統合するコンポーネントを作成する方が簡単です。

こちらが 2008年のフィールディングの執筆

RESTは、複数の組織にまたがる長期間有効なネットワークベースのアプリケーションを対象としています。

「長期」を管理する方法の1つは、渡すメッセージのセマンティクスを説明する明確な標準を持つことです。すべての人がPUTの意味に同意する場合、それらのリクエストのコンシューマーとプロデューサーは独立して開発でき、2つの間の中間コンポーネントは、特定のコンテキストでメッセージの詳細を知る必要なく、賢明なアクションを実行できます。

PUT/PATCH-変更のためにリソース全体を渡す意味は何ですか?リソースの変更にはPUTのみを使用し、更新するフィールドのみを渡します。その結果、パッチを使用する必要はありません

PUTを使用する意味は何ですか?

PURPLE /014d8c83-604d-4cf0-a6ba-e1f7ef8c4898 HTTP/1.1
...

これは、HTTPメッセージに対する完全に有効な要求行であり、セマンティクスの変更によって混乱することはありません。

同等に

POST /014d8c83-604d-4cf0-a6ba-e1f7ef8c4898 HTTP/1.1
...

どちらも意味の制約がほとんどありません。サーバーは、その要求の処理を任意の方法で実装できます。

jSON REST APIについて話していると仮定します

JSONパッチ および JSONマージパッチ の標準がすでに提案されているため、PATCHを使用することへの抵抗は特に奇妙です。パッチドキュメント形式を標準化する作業は、あなたのためにすでに行われています。

別の有効な代替手段は、パッチドキュメントを個別のリソースとして扱うことです。意味的に、あなたは次のようなものを想像するかもしれません

PUT /014d8c83-604d-4cf0-a6ba-e1f7ef8c4898/patches/5c42c414-03c0-4ac5-af14-2b1165ac98b3 HTTP/1.1

これにより、正直で統一されたメッセージセマンティクスが得られ、標準化された キャッシュの無効化 が犠牲になります。

コードレビューの設定では、PUTのセマンティクスを再定義しようとする提案された変更を拒否します。

HTTPは、GETの結果が安全であることを要求することを試みません。それが行うことは、操作のセマンティクスが安全であることを要求することであり、その結果、プロパティの損失を引き起こす結果として何かが発生した場合(インターフェイス、そのインターフェイスのユーザーではなく)、実装の障害です(お金、BTW、この定義のためにプロパティと見なされます)。 - フィールディング、2002

同じ考慮事項がPUTにも当てはまります。 PUTの実装が標準化されたセマンティクスから逸脱している場合、その結果の損傷は実装に責任があります。

リソースパス-アプリケーションでGUIDを使用しています。その結果、それらは世界的にユニークになります。単独でサブリソースを一意に参照できるのに、親リソースを含む完全なリソースパスが必要なのはなぜですか?

まったく問題ありません。 RESTは、リソース識別子にどのスペルを使用してもかまいません。

グーグルランディングページを検討してください。今日のDoodleのURIのスペルに注意を払う必要がありますか?または検索フォームはどこに送信されていますか?いいえ、もちろん、違います。 HTMLペイロードにはURIが含まれており、クライアントは提供された識別子を標準的な方法で使用するだけで、それらの識別子を分析する必要はありません。

URIにエンコードされた情報は、それ自体の目的のために、Originサーバーの裁量にあります。

APIのエントリポイントとしてこのようなURIを使用することはお勧めしません。 https://www.example.org/df8f5f87-15ff-4212-8fb8-4fbca2c7efcfは、人間の消費にとって少し厄介です。 UUIDリソースにリダイレクトする人間が読めるURIは問題ありませんが、UUIDリソースのコンテンツを返す人間が読めるURIの方が優れています。

POSTで新しいリソースを作成する、PUTで​​更新する、またはDELETEで削除する場合、作成/更新/削除したリソースを表示するだけでなく、ツリーでデルタを表示したい

それで結構です-もう一度、 standard を見てください。

200応答で送信されるペイロードは、要求メソッドによって異なります。この仕様で定義されているメソッドの場合、ペイロードの意図する意味は次のように要約できます。

アクションのステータスまたはアクションから取得した結果の表現をPOST

アクションのステータスの表現をPUT、DELETE

場合によっては、リソースの新しい表現を応答の一部として送信することが理にかなっています(クライアントにGET要求/応答の待ち時間を節約するため)。

9
VoiceOfUnreason

RESTは、ステートレスな方法でリソースを操作するためのアーキテクチャスタイルです(ステートレスとは、各操作がそれ自体に依存し、実行された可能性のある他の操作に依存しないことを意味します)。

動詞PUT/PATCH/POST/GET/DELETEの使用は、リソースの転送と操作に使用されるHTTPプロトコルの一般的な使用から来ています。これらの動詞の意味は、インターネット標準( RFC7231 )で定義されています。

その背景を考えると、PUTの使用は非標準であり、APIを使用したい他の開発者を混乱させる可能性があります。

リソースパスについては、正確なスペル(子リソースが子としてリストされている場合を含む)を気にする人はほとんどいません。人々が気にするのは、各リソースが一意に識別されることです。システム /parent/:pid/child/:cidは、子IDが1つの親内でのみ一意であり、リソースへのグローバルに一意のパスを持っている場合によく使用されます。

  1. PUT/PATCH-変更のためにリソース全体を渡す意味は何ですか?リソースの変更にはPUTのみを使用し、更新するフィールドのみを渡します。その結果、パッチを使用する必要はありません

ここでは実際にPATCHをセマンティクスとして使用する必要があるようです。

  • PUTは正確な望ましい状態を説明します。これは、リソースが頻繁に変更される可能性があり、特定のコンテキスト内で必要な変更を行う必要がある場合に役立ちます。

  • PATCHは、目的のデルタをそこにあるものと説明します。これは、変更がコンテキストを気にしない場合、または関連するコンテキストがリソース全体よりもはるかに小さい場合に役立ちます。

画像は、全体をアップロードするだけで意味がある場合の良い例です。関連するコンテキストを確保するために、リソース全体を伝達することが重要です。

逆に、音楽プレイリストの再生回数を更新することは、デルタである方が理にかなっている場合があります。小さな変更ではなく、リスト全体を再送信すると、リストのコンテンツへの変更を簡単に取り消すことができるためです。

  1. リソースパス-アプリケーションでGUIDを使用しています。その結果、それらは世界的にユニークになります。単独でサブリソースを一意に参照できるのに、親リソースを含む完全なリソースパスが必要なのはなぜですか?

...をちょきちょきと切る...

それだけでいいの?私のパスに、指定された:childIdによって実際には所有されていない:subresourceIdと、親:idによって所有されていない:childIdのディットが含まれている場合、追加のエラー処理が必要になるためです。私のサーバー側は、リソースの承認を担当しています。フルパスではなく、リソース自体を参照することはできませんか?

いいえ、パスを使用する必要はありません-これまで。すべてのファイルをデスクトップに保存していると言いますか?番号? なぜそうではないのですか?

おそらく、見やすくするために何かをする必要があります。ここでも同じ問題があります。 A GUIDは、これを設定、デバッグ、または実行しているときに、何を処理しているかを通知しません。

それでは、このシステムをサポートすることはどのように感じますか?それと対話するために?それについて考えていない場合は、しばらく時間がかかり、ラインを押し下げている作業を検討してください。

明示的なパス情報があると、リクエストの検証に役立ちます。また、サポートやダウンストリームのシステム開発者がこれらのURLにアプローチして使用できるように、情報を十分に区別するのにも役立ちます。

  1. 戻り応答。たとえば、私のデータ構造が階層ツリーであり、ツリーの深さに実質的な制限がないとします。リソースは、ツリーのさまざまなレベルに階層的に配置されています。

    a。 GETは明白です。このツリー全体を取得した場合、リソース全体がリソース内に含まれた状態で、ツリー全体が応答として期待されます。

深さの制限を課したい場合があります。単純に、一部の賢い子供が、考えられるすべての操作でサイトのルートを取得しないようにするためです。

b。私がPOSTで新しいリソースを作成する、PUTで​​更新する、またはDELETEで削除する場合、作成/更新/削除したリソースを表示するだけでなく、ツリーでデルタを表示したい。特にPOST、PUT、またはDELETEを実行するたびに、親ツリーのGETを再度呼び出す必要はありません。特に、ツリーにほとんど変更がなく、デルタのみを表示したい場合に使用します。

リソースの更新が重要で予測不可能な方法で親に影響を与える場合は、他の問題があります...状態モデルを調べて、情報が飛び交う理由を理解する必要があります。

デルタの項目別リストのみを返したい場合は、どうしてですか?さまざまなパラメーターによって切り替えられる複数の出力ビューをサポートしないのはなぜですか? Jenkinsはxmlまたはjsonの選択でAPI応答を返し、目的のサブツリーを抽出するためのいくつかのフィルターを指定できます。

自分で使う

率直に言って、作成しているものから一歩下がってサポートするか、別のアプリケーションを作成して使用します(既存のアプリケーションではありません)。サードパーティのAPIについても同様のことを行い、バックグラウンドのコンテキストを少しだけ残します。

サポートリクエストを直接解決しない、またはクライアントアプリケーションに直接必要ではない何かをしなければならない場合、APIは理想的ではなく、なぜそれが理想的ではないのかを知っています。修正するか、同じ間違いをしないようにしてください。

たとえば、特定のURLへのリクエストが常に失敗している場合、失敗しているものとその理由を特定するためにどれだけの労力を費やす必要がありますか?どのような手順を踏みましたか。URIを改善したり、ロギングを改善したり、監視を改善したりすることで、これらの手順の1つを回避できましたか?

同様に、新しいクライアントを作成している場合、ドキュメントまたはAPIのソースコードを何回参照する必要がありますか?その必要性を減らすために何ができますか?自分の期待に反するのをやめるために何ができますか?サーバーを維持するのが悪夢になることなく、クライアントアプリケーションの問題を単純化するにはどうすればよいでしょうか。

正しい方法

正直なところ、正しい方法は状況に応じたものです。 RESTは、いくつかの問題に対してさまざまな状況で機能することが示されている一連のプラクティスです。問題が当てはまらない場合は、当てはまらないでください。それらの慣行を使用すると主張する。

1
Kain0_0