コンテンツネゴシエーションを使用しない傾向がある理由

カーソン・グロス

ハイパーメディア API 対データ (JSON) API について、数多く執筆してきました。たとえば、2 つの相違点、REST の「真の」意味や REST の意味がどうして REST と正反対になったか、および API が ハイパーメディアクライアント と対話している限り、HATEOAS はさほど悪くはない理由などです。

多くの場合、「REST は HTTP 経由の JSON」という世界 (つまり、通常の世界) 出身のユーザーと議論していると、多くの言語的な問題や概念的な問題に遭遇します。

この最後の要望は、単一の汎用 JSON API に慣れている人たちには、何か馬鹿げたこととして映ることがよくあります。あらゆる種類のクライアントに対応できる単一の API があるのに、なぜ API が 2 つ必要なのでしょうか。先のエッセイでできる限り、その疑問に答えようと試みましたが、確かに質問するのは適切です。

何らかの点では、汎用 API を 1 つ持つ場合と比べて、さらに作業が必要であるように (実際、その通りです) 思われます。

この場合の会話では、REST に関する私の見解、ハイパーメディア駆動型アプリケーション などに概ね賛成する人が、しばしば割り込んで次のように言います。

「ああ、簡単です。コンテンツネゴシエーション を使うだけです。HTTP に組み込まれていますから。」

私は、汎用 JSON API 愛好家だけでなく、元からのハイパーメディア愛好家の同盟者も疎外するつもりなので、次のことを言います。

ほとんどのアプリケーションでは、JSON と HTML の両方を返す場合、コンテンツネゴシエーションが典型的な正しいアプローチではないと思います。

#コンテンツネゴシエーションとは何か

まず最初に、「コンテンツネゴシエーション」とは何でしょうか。

コンテンツネゴシエーション は、クライアントがサーバーからのレスポンスのコンテンツタイプをネゴシエーションできる HTTP の機能です。HTTP での実装について十分に説明するのは、このエッセイの範囲を超えています。しかし、HTTP で最もよく知られているコンテンツネゴシエーションメカニズム、つまり Accept リクエストヘッダー を考えてみましょう。

Accept リクエストヘッダーを使用すると、ブラウザーなどのクライアントは、レスポンスでサーバーから受け入れることができる MIME タイプを指定できます。

このヘッダーの値の一例は次のとおりです。

Accept: text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, */*;q=0.8

この Accept ヘッダーは、クライアントがどの形式を受け入れることができるかをサーバーに伝えます。優先順位は q 重み付けファクターで表されます。ワイルドカードはアスタリスク * で表されます。

この場合、クライアントは次のことを述べています。

text/html、application/xhtml+xml、image/webp を最も受け取りたいです。次に application/xml を優先します。最後に、提供されるものを受け取ります。

次にサーバーはこの情報を受け取って、クライアントに提供する最善のコンテンツタイプを決定できます。

これが「コンテンツネゴシエーション」の行為であり、確かに HTTP の興味深い機能です。

#API でのコンテンツネゴシエーションの使用方法

私の知る限り、コンテンツネゴシエーションを利用して、1つのURLからHTMLとJSON(およびその他)の両方の形式を提供したのはRuby On Railsコミュニティが最初だったと思います。

Railsでは、これはコントローラー内で使用可能なrespond_toヘルパーメソッドによって実現されています。

Railsの面倒な詳細を脇に置いておくと、/contactsへのHTTP GETのようなリクエストがあり、それがContactsControllerクラスの関数呼び出しに終わるものがあるとします。その関数は次のようになります。

def index
  @contacts = Contacts.all

  respond_to do |format|
    format.html # default rendering logic
    format.json { render json: @contacts }
  end
end

respond_toヘルパーメソッドを利用することで、クライアントが上記のようなAcceptヘッダーでリクエストを行うと、コントローラーはRailsのテンプレートシステムを使用してHTMLレスポンスをレンダリングします。

しかし、クライアントのAcceptヘッダーの値が代わりにapplication/jsonの場合、Railsは、クライアント向けのJSON配列として連絡先をレンダリングします。

非常に便利なトリックです。連絡先を調べるなどの、コントローラーのロジックはそのままにして、ちょっとしたruby/Railsのマジックを使用して、コンテンツネゴシエーションによって2つの異なるレスポンスの種類をレンダリングできます。通常のモデル/ビュー/コントローラーロジックに追加する作業はほとんど必要ありません。

人々がこのアイデアを好む理由が分かるでしょう。

#では、問題は何ですか?

では、私がこれがJSONとHTML APIを分割するための良いアプローチではないと思う理由はなぜでしょうか。

これは、以前少し触れたJSON APIとハイパーメディア(HTML)APIの違いに要約されます。特に次のとおりです。

これらの違いはすべて重要であり、コントローラーコードに影響を与え、異なる2つの方向に引っ張られますが、実際には最初と最後のアイテムが私によくアプリケーションでコンテンツネゴシエーションを使用しないようにさせています。

JSON APIは、クライアントコードが信頼できる安定したエンドポイントのセットである必要があります。

一方、ハイパーメディアAPIは、アプリケーションのユーザーインターフェイスのニーズに基づいて大幅に変更できます。

この2つはうまく調和しません。

具体的な例を挙げると、/contacts/:id:idはレンダリングする連絡先のIDを含むパラメーターです)など、連絡先の詳細ビューをレンダリングするエンドポイントを考えてみましょう。このページにはUIの「関連連絡先」セクションがあり、さらに、これらの関連連絡先を計算することが何らかの理由でコストがかかるとします。

この状況では、遅延読み込みパターンを使用して、最初の連絡先詳細画面のレンダリング後に関連連絡先のロードを遅延させることができます。これにより、ユーザーのページの体感的なパフォーマンスが向上します。

これをした場合、遅延読み込みコンテンツをエンドポイント/contacts/:id/relatedに入れることができます。

さて、後で関連連絡先の計算を最適化できるかもしれません。この時点で、/contacts/:id/relatedエンドポイントを削除して、関連連絡先情報を最初のページレンダリングにレンダリングすることを選択する場合があります。

これはすべて、ハイパーメディアAPIにとっては問題ありません。ハイパーメディアは、ユニフォームインターフェイス&HATEOASを介して、このような変更を処理するように設計されています。

しかし、JSON APIは…それほどではありません。

JSON API は安定しておかなければなりません。原則無視でエンドポイントを追加したり削除したりすることはできません。一部のエンドポイントは JSON または HTML で応答し、他のエンドポイントは HTML でのみ応答することはできますが、混乱を招きます。たとえば、間違ってコードをコピーして貼り付けないようにする必要がある場合などです。

レート制限などの要素を考慮すると、JSON API とハイパーメディア API の間に 懸念の分離 が必要であると強く主張できます。

局所的な動作 の用語を考案した人物が SoC の議論をしているのは皮肉なことだと認識しています。)

#だから代替案は何ですか?

代替案は、API を分割するで私が提唱しているように、ええと、つまりは API を分割することです。つまり、JSON API とハイパーメディア(HTML)API に別のパス(またはサブドメインなど)を提供することです。

連絡先 API に戻ると、以下のようなものがあります。

このレイアウトは 2 つ異なるコントローラーを示唆しており、それは良いことだと私は言います。JSON API コントローラーは、レート制限、安定性、GraphQL などの表現力豊かなクエリメカニズムなどの JSON API の要件を実装できます。

一方で、ハイパーメディア API(実際には、ハイパーメディア駆動アプリケーションのエンドポイントだけ)は、ユーザーインターフェースのニーズが変更されるにつれて大幅に変更でき、データベースクエリが高度に調整され、特別な UI ニーズをサポートするエンドポイントなどが含まれます。

これら 2 つの懸念を分離することにより、JSON API は安定していて、規則的で、メンテナンスが少なくなり、ハイパーメディア API は混乱を招き、特殊化され、柔軟になります。それぞれが独自の controller 環境で繁栄し、互いに競合することはありません。

そして、HTTP コンテンツネゴシエーションを使用して 2 つのエンドポイントにコントローラーを再利用しようとするのではなく、JSON API とハイパーメディア API を個別のコントローラーに分割することを好む理由がこれです。

</>