web-dev-qa-db-ja.com

REST APIクライアントとしてのWebアプリケーション:リソース識別子の処理方法

RESTに関連するいくつかの概念は、実装しようとすると頭の中で矛盾します。

ビジネスロジックを保持するRESTフルバックエンドAPIシステムと、UIを提供するWebアプリケーションがあります。 REST(特に 実際のREST:ハイパーメディアとシステムアーキテクチャ )に関するさまざまなリソースから、エンティティの生の識別子を公開するのではなく、ハイパーリンクを返す必要があることを知っていますrel="self"

例を考えてみましょう。 REST apiには人を返すリソースがあります:

<Person>
  <Links>
    <Link rel="self" href="http://my.rest.api/api/person/1234"/>
  </Links>
  <Pets>
    <Link rel="pet" href="http://my.rest.api/api/pet/678"/>
  </Pets>
</Person>

Webアプリケーションで問題が発生します。ブラウザへのハイパーリンクを含むページを返すとしましょう:

<body class="person">
  <p>
    <a href="http://my.web.app/pet/???????" />
  </p>
</body>

href属性には何を入れればよいですか?ユーザーがターゲットページを開いたときにエンティティを取得できるように、WebアプリケーションでAPIエンティティのURLを保持するにはどうすればよいですか?

要件は矛盾しているようです:

  1. ハイパーリンクhrefは、UIをホストするシステムであるため、Webアプリケーションにつながるはずです。
  2. hrefにはエンティティのいくつかのIDが必要です。これは、ターゲットページが開いたときにWebアプリがエンティティをアドレス指定できる必要があるためです。
  3. Webアプリケーションは、RESTフルではないため、URLを解析/構築しないでくださいREST URL

URIは消費者に対して不透明でなければなりません。 URIの発行者だけがそれを解釈してリソースにマップする方法を知っています。

したがって、RESTfulクライアントとして1234のように扱う必要があるため、API応答URLからhttp://my.rest.api/api/AGRIDd~ryPQZ^$RjEL0jを取得することはできません。一方、Webアプリにつながる、アプリがAPIの元のURLを何らかの方法で復元し、そのURLを使用してAPIリソースにアクセスするのに十分なURLを指定する必要があります。

最も簡単な方法は、リソースのAPI URLを文字列識別子として使用することです。しかし、http://my.web.app/person/http%3A%2F%2Fmy.rest.api%2Fapi%2Fperson%2F1234のようなWebページのURLは醜いです。

デスクトップアプリや単一ページのJavaScriptアプリでは、すべてが非常に簡単に思えます。それらは継続的に存在するため、アプリケーションの存続期間中、サービスオブジェクトと共にURLをメモリに保持し、必要に応じてそれらを使用できます。

Webアプリを使用すると、いくつかのアプローチを想像できますが、すべて奇妙に見えます。

  1. API URLのホストを置き換え、結果のみを保持します。大きな欠点は、APIが生成するすべてのURLを処理するWebアプリケーションが必要になることです。これは、巨大な結合を意味します。さらに、私のWebアプリがURLの解釈を開始するため、RESTfulにはなりません。
  2. REST APIで未加工のIDをリンクと一緒に公開し、それらを使用してWebアプリのURLを作成します。次に、WebアプリサーバーでIDを使用して、APIで必要なリソースを見つけます。これは優れていますが、WebアプリはRESTサービスナビゲーションを通過して、何らかのリクエストのget-by-idリクエストのチェーンを発行し、あらゆるリクエストを処理する必要があるため、Webアプリサーバーのパフォーマンスに影響しますブラウザから。ややネストされたリソースの場合、これはコストがかかる可能性があります。
  3. APIによって返されたすべてのself URLをWebアプリサーバーの永続的な(DB?)マッピングに保存します。それらのIDを生成し、IDを使用してWebアプリページのURLを作成し、RESTサービスリソースのURLを取得します。つまり、http://my.rest.api/pet/678 URLを新しいキー、「3」と言い、WebページのURLをhttp://my.web.app/pet/3として生成します。これは、ある種のHTTPキャッシュ実装のように見えます。理由はわかりませんが、奇妙なようです。

それとも、RESTful APIがWebアプリケーションのバックエンドとして機能できないということですか?

21
Pavel Gatilov

質問の更新に対処するために編集され、以前の回答は削除されました

質問の変更点を確認すると、もう少し問題が発生していることがわかります。リソースの識別子であるフィールド(リンクのみ)がないため、GUI内でその特定のリソース(つまり、特定のペットを説明するページへのリンク)を参照する方法はありません。

最初に決定することは、飼い主がいないペットに意味があるかどうかです。飼い主がいないペットを飼うことができるのであれば、ペットを参照するために使用できる、ある種のユニークなプロパティが必要だと思います。 RESTクライアントが解析しないリンクに実際のリソースIDが隠されているため、これがIDを直接公開しないことには違反しないと思います。ペットリソースを念頭に置いてください。次のようになります。

<Entity type="Pet">
    <Link rel="self" href="http://example.com/pets/1" />
    <Link rel="owner" href="http://example.com/people/1" />
    <UniqueName>Spot</UniqueName>
</Entity>

これで、アプリケーション全体で実際にリソースIDをいじる必要なく、そのペットの名前をSpotからFidoに更新できます。同様に、GUIでそのペットを次のように参照できます。

http://example.com/GUI/pets/Spot

飼い主なしではペットが意味をなさない場合(または飼い主なしではシステムでペットが許可されない場合)、システム内のペットの「ID」の一部として飼い主を使用できます。

http://example.com/GUI/owners/John/pets/1 (ジョンのリストの最初のペット)

小さな注意点として、ペットとピープルの両方がお互いに分離して存在できる場合、APIのエントリポイントを「ピープル」リソースにしません。代わりに、People and Petsへのリンクを含むより一般的なリソースを作成します。次のようなリソースを返す可能性があります。

<Entity type="ResourceList">
    <Link rel="people" href="http://example.com/api/people" />
    <Link rel="pets" href="http://example.com/api/pets" />
</Entity>

したがって、APIへの最初のエントリポイントのみを認識し、システム識別子を把握するためにURLを処理しないことで、次のようなことができます。

ユーザーがアプリケーションにログインします。 RESTクライアントは、次のような利用可能な人的リソースのリスト全体にアクセスします。

<Entity type="Person">
    <Link rel="self" href="http://example.com/api/people/1" />
    <Pets>
        <Link rel="pet" href="http://example.com/api/pets/1" />
        <Link rel="pet" href="http://example.com/api/pets/2" />
    </Pets>
    <UniqueName>John</UniqueName>
</Entity>
<Entity type="Person">
    <Link rel="self" href="http://example.com/api/people/2" />
    <Pets>
        <Link rel="pet" href="http://example.com/api/pets/3" />
    </Pets>
    <UniqueName>Jane</UniqueName>
</Entity>

GUIは各リソースをループし、UniqueNameを "id"として使用して各ユーザーのリストアイテムを出力します。

<a href="http://example.com/gui/people/1">John</a>
<a href="http://example.com/gui/people/2">Jane</a>

これをしている間、それはそれが「ペット」のrelで見つけたそれぞれのリンクを処理して、以下のようなペットリソースを取得することもできます:

<Entity type="Pet">
    <Link rel="self" href="http://example.com/api/pets/1" />
    <Link rel="owner" href="http://example.com/api/people/1" />
    <UniqueName>Spot</UniqueName>
</Entity>

これを使用すると、次のようなリンクを印刷できます。

<!-- Assumes that a pet can exist without an owner -->
<a href="http://example.com/gui/pets/Spot">Spot</a>

または

<!-- Assumes that a pet MUST have an owner -->
<a href="http://example.com/gui/people/John/pets/Spot">Spot</a>

最初のリンクに進み、エントリリソースに「ペット」のリレーションを持つリンクがあると仮定すると、制御フローはGUIで次のようになります。

  1. ページが開き、ペットスポットがリクエストされます。
  2. APIエントリポイントからリソースのリストをロードします。
  3. 「ペット」という用語に関連するリソースをロードします。
  4. 「pets」応答から各リソースを調べ、Spotに一致するものを見つけます。
  5. スポットの情報を表示します。

2番目のリンクの使用は、PeopleがAPIへのエントリポイントであり、最初にシステム内のすべての人、一致するものを見つけ、その人に属するすべてのペットを見つけ(再度relタグを使用)、Spotという名前の人を見つけて、それに関連する特定の情報を表示できるようにします。

5
Mike

それはすべて、RESTful APIがWebアプリケーションのバックエンドとして機能できないことを意味しますか?

REST APIとWebアプリケーションを区別する価値があるかどうかに挑戦します。「Webアプリケーション」は、同じリソースの代替(HTML)表現である必要があります。つまり、 http://my.rest.api/...http://my.web.app/...にアクセスする方法と理由、およびそれらが同時に同じで異なることを理解していない。

この場合の「クライアント」はブラウザであり、HTMLとJavaScriptを理解します。それはis私の意見ではWebアプリケーションです。今、あなたは同意せず、foo.comを使用して上記のWebアプリケーションにアクセスし、api.foo.comを介して他のすべてを公開していると思うかもしれません-しかし、次に質問する必要があります。 foo.comの「バックエンド」は、api.foo.comからリソースを発見する方法を完全に理解できます。 Webアプリケーションは単にプロキシになりました-他のAPIと(他の誰かから)一緒に話している場合と同じです。

したがって、質問を一般化して、「他のシステムに存在する自分のURIを使用してリソースをどのように説明できますか?」これを行う方法を理解する必要があるのはクライアント(HTML/JavaScript)ではなく、サーバーであると考えると、これは簡単です。私の最初の課題に同意する場合は、Webアプリケーションを別のREST APIにプロキシまたは委任する別のREST API。

したがって、クライアントがmy.web.app/pets/1にアクセスすると、サーバー側のテンプレートから返されたものか、他の表現(JSONやXMLなど)に対する非同期リクエストの場合、コンテンツがタイプヘッダーはそれを伝えます。

これを提供するサーバーは、ペットとは何かを理解し、リモートシステムでペットを発見する方法を担当するサーバーです。これをどのように行うかはあなた次第です。単にIDを取得して別のURIを生成することができますが、これは不適切だと思うか、リモートURIを格納してリクエストをプロキシする独自のデータベースを持つことができます。このURIの保存は問題ありません。ブックマークに相当します。個別のドメイン名を持つためだけにこれをすべて行うことになります。なぜこれが必要なのか正直にわかりません。REST API URIもブックマーク可能でなければなりません。

あなたはすでにあなたの質問でこれのほとんどを取り上げましたが、あなたがやりたいことを行うための実用的な方法であることを本当に認識しなかった方法でそれを組み立てたと思います(私が感じていることに基づいて)任意の制約-APIとアプリケーションが別々であること)。 REST APIはWebアプリケーションのバックエンドにはなり得ないかどうかを尋ね、パフォーマンスが問題になることを示唆することで、あなたはすべてを間違っていることに集中していると思います。マッシュアップを作成できません。まるでウェブが動かないと言っているようなものです。

6
Doug

序文

この回答は、バックエンドREST APIが識別子を明示的に公開せず、URLを解釈せずに、リソースの一意のブックマーク可能なURLを含む、独自のURLスキームを管理する方法の問題に特に対処しますAPIによって提供されます。


発見可能性にはある程度の知識が必要なので、実際のシナリオでの私の見方を次に示します。

http://my.web.app/personに検索ページが必要だとします。この検索結果には、各ユーザーの詳細ページへのリンクが含まれています。フロントエンドコードmustが何でも実行するために知っていることの1つは、そのRESTデータソース:http://my.rest.api/api。このURLへのGETリクエストに対する応答は次のようになります。

<Links>
    <Link ref="self" href="http://my.rest.api/api" />
    <Link rel="person" href="http://my.rest.api/api/person" />
    <Link rel="pet" href="http://my.rest.api/api/pet" />
</Links>

私たちの意図は人のリストを表示することなので、次にGETリンクhrefからhrefにpersonリクエストを送信します。

<Links>
    <Link ref="self" href="http://my.rest.api/api/person" />
    <Link rel="search" href="http://my.rest.api/api/person/search" />
</Links>

検索結果を表示したいので、GETリクエストをsearchリンクhrefに送信して検索サービスを使用します。

<Persons>
    <Person>
        <Links>
            <Link rel="self" href="http://my.rest.api/api/person/1"/>
        </Links>
        <Pets>
            <Link rel="pet" href="http://my.rest.api/api/pet/10"/>
        </Pets>
    </Person>
    <Person>
        <Links>
            <Link rel="self" href="http://my.rest.api/api/person/2"/>
        </Links>
        <Pets>
            <Link rel="pet" href="http://my.rest.api/api/pet/20"/>
        </Pets>
    </Person>
</Persons>

ようやく結果が出ましたが、フロントエンドURLをどのように構築するのでしょうか。

確かにわかっている部分、APIベースURLを取り除き、残りをフロントエンド識別子として使用します。

  • 既知のAPIベース:http://my.rest.api/api
  • 個々のエンティティの指定されたURL:http://my.rest.api/api/person/1
  • 一意のID:/person/1
  • ベースURL:http://my.web.app
  • 生成されたフロントエンドURL:http://my.web.app/person/1

結果は次のようになります。

<ul>
    <li><a href="http://my.web.app/person/1">A person</a></li>
    <li><a href="http://my.web.app/person/2">A person</a></li>
</ul>

ユーザーが詳細ページへのフロントエンドリンクをたどったら、特定のGETの詳細を求めるpersonリクエストをどのURLに送信しますか?バックエンドURLをフロントエンドURLにマッピングする方法はわかっているので、それを逆にします。

  • フロントエンドURL:http://my.web.app/person/1
  • ベースURL:http://my.web.app
  • 一意のID:/person/1
  • 既知のAPIベース:http://my.rest.api/api
  • 生成されたAPI URL:http://my.rest.api/api/person/1

REST APIが変更され、person URLがhttp://my.rest.api/api/different-person-base/person/1になり、誰かが以前にhttp://my.web.app/person/1をブックマークしていた場合、REST APIは(少なくともしばらくの間)、新しいURLへのリダイレクトで古いURLに応答することにより、下位互換性を提供する必要があります。生成されたすべてのフロントエンドリンクには、新しい構造が自動的に含まれます。

おそらくお気づきのとおり、APIをナビゲートするために知っておくべきことがいくつかあります。

  • aPIベースURL
  • personリレーション
  • searchリレーション

これには何も問題はないと思います。特定のURL構造を想定しているわけではないため、エンティティURL http://my.rest.api/api/person/1の構造は変更される可能性があり、APIが下位互換性を提供している限り、コードは引き続き機能します。


ルーティングロジックが2つのフロントエンドURLの違いをどのように区別できるかを質問しました。

  • http://my.rest.api/api/person/1
  • http://my.rest.api/api/pet/3

最初に、コメントでAPIベースを使用したことを指摘します。この例では、UIとREST APIに別々のベースURLを使用しています。この例を続けます。別のベースを使用しますが、ベースの共有は問題ではありません。リクエストのAcceptヘッダーのメディアタイプを使用して、UIルーティングメソッドをマッピングできます(またはできるはずです)。

特定の詳細ページへのルーティングについては、APIによって提供されるself URLの構造に関する知識(つまり、不透明な文字列ID)を回避することが厳格である場合、これら2つのURLを区別することはできません。これを機能させるために、フロントエンドURLに既知の情報のもう1つ(使用しているエンティティタイプ)を含めます。

以前は、フロントエンドURLは次の形式でした:${UI base}/${opaque string id}

新しい形式は次のようになります:${UI base}/${entity type}/${opaque string id}

したがって、/person/1の例を使用すると、http://my.web.app/person/person/1になります。

この形式では、UIルーティングロジックは/person/person/1で機能し、文字列の最初のトークンが自分で挿入されたことがわかっているので、それを引き出して適切な(この例では人)詳細にルーティングできます。それに基づくページ。そのURLについて気分が悪い場合は、そこにもう少し挿入できます。多分:http://my.web.app/person/detail/person/1

その場合、ルーティングのために/person/detailを解析し、残りを不透明な文字列IDとして使用します。


これは、WebアプリとAPIの非常に緊密な結合をもたらすと思います。

生成されたフロントエンドURLにAPI URLの一部が含まれているため、古い構造をサポートせずにAPI URL構造が変更された場合、ブックマークされたURLをAPI URLの新しいバージョン。言い換えると、REST APIがリソースのID(不透明な文字列)を変更する場合、古いIDを使用してそのリソースについてサーバーと通信することはできません。そのような状況ではコードの変更を回避できます。

WebアプリのURL構造をAPIのURL構造と異なるようにしたい場合はどうなりますか?

任意のURL構造を使用できます。結局のところ、特定のリソースのブックマーク可能なURLには、そのリソースを一意に識別するAPI URLを取得するために使用できるものを含める必要があります。独自の識別子を生成し、アプローチ#3のようにAPI URLでそれをキャッシュする場合、そのエントリがキャッシュからクリアされた後、誰かがそのブックマークされたURLを使用しようとするまで機能します。

WebアプリのエンティティがAPIエンティティ1-1にマッピングされなかった場合はどうなりますか?

答えは関係によって異なります。どちらの方法でも、フロントエンドをAPI URLにマップする方法が必要になります。

5
Mike Partridge

それに直面しよう、魔法の解決策はありません。 リチャードソン成熟度モデル を読みましたか? RESTアーキテクチャの成熟度を、リソース、HTTP動詞、ハイパーメディアコントロールの3つのレベルに分割します。

エンティティの未加工の識別子を公開するのではなく、rel = "self"のハイパーリンクを返す必要があります

これはハイパーメディアコントロールです。本当に必要ですか?このアプローチには、いくつかの非常に優れた利点があります(それらについて こちら で読むことができます)。しかし、無料の食事などというものはなく、もしあなたがそれらを手に入れたいなら、あなたは一生懸命働かなければならないでしょう(例えば、2番目の解決策)。

それはバランスの問題です。パフォーマンスを犠牲にして(そしてコードをより複雑に)したいのですが、より柔軟なシステムを得たいですか?それとも、物事をより速く簡単にして、API /モデルに変更を導入するときに後で支払うことを好みますか?

同様のシステム(ビジネスロジック層、Web層、Webクライアント)を開発した人として、2番目のオプションを選択しました。私のグループはすべての層を開発したので、(Web層にエンティティIDとビルドAPIのURLを知らせることによって)少し結合して、代わりに、より単純なコードを取得することをお勧めします。下位互換性も私たちのケースでは関係ありませんでした。

Webアプリケーションがサードパーティによって開発された場合、または下位互換性が問題である場合、Webアプリケーションを変更せずにURL構造を変更できることに大きな価値があったため、別の方法を選択した可能性があります。コードの複雑化を正当化するのに十分です。

それはすべて、RESTful APIがWebアプリケーションのバックエンドとして機能できないことを意味しますか?

完璧なREST実装を作成する必要がないことを意味します。2番目のソリューションを使用するか、エンティティIDまたは多分 パスを公開できますapi urls 。意味とトレードオフを理解していれば問題ありません。

2
daramasala

最も簡単な方法は、リソースのAPI URLを文字列識別子として使用することです。しかし http://my.web.app/person/http%3A%2F%2Fmy.rest.api%2Fapi%2Fperson%2F1234 のようなWebページのURLは醜いです。

私はあなたが正しいと思います、それが最も簡単な方法です。 URLをhttp://my.rest.api/apiに対して相対化して、見栄えをよくすることができます。

http://my.web.app/person/person%2F1234

APIによって提供されたURLがそのベースに対して相対的でない場合、醜い形式に低下します。

http://my.web.app/person/http%3A%2F%2Fother.api.Host%2Fapi%2Fperson%2F1234

さらに一歩進むには、APIサーバーからの応答を調べて、表示するビューの種類を決定し、パスセグメントの区切り文字とコロンのエンコードを停止します。

http://my.web.app/person/1234 (best case)
http://my.web.app/http://other.api.Host/api/person/1234 (ugly case)
0
ajlane

Atom Syndication Formatあなたは元気です。

ここで、表されているエントリを説明するメタデータは、追加の要素/属性を使用して指定できます。

  • [RFC4287] のように、エントリを一意に識別するURIが含まれています

  • [RFC4287] に従って、この要素はオプションです。含まれている場合は、クライアントがエントリを取得するために使用する必要があるURIが含まれます。

これはちょうど私の2セントです。

0
rusev

URLを気にしないでください。メディアタイプを気にしてください。

ここを参照 (特に3番目の箇条書き)。

A REST APIは、リソースの表現とアプリケーションの状態の駆動に使用されるメディアタイプの定義、または拡張リレーション名やハイパーテキスト対応マークの定義に、そのほとんどすべての記述的努力を費やすべきです既存の標準メディアタイプのアップ...


典型的なWebアプリの場合、クライアントはhuman;です。ブラウザは単なるagentです。

ので、アンカータグ

          <a href="example.com/foo/123">click here</a>

のようなものに対応します

          <link type="text/html" rel="self" href="example.com/foo/123">

URLは依然としてユーザーには不透明であり、ユーザーが気にするのはメディアタイプのみです(例:text/html, application/pdf, application/flv, video/x-flv, image/jpeg, image/funny-cat-picture etc)。アンカー(およびタイトル属性)に含まれる説明テキストは、人間が理解できる方法で関係タイプを拡張するための単なる方法です。

URIをクライアントに対して不透明にしたいのは、結合を減らすためです(RESTの主な目的の1つ)。サーバーは、クライアントに影響を与えることなくURIを変更/再編成できます(適切なキャッシングポリシーがある限り(つまり、キャッシングがないことを意味する場合があります)。

要約すれば

クライアント(人間またはマシン)がURLではなくメディアタイプと関係に注意を払うことを確認してください。

0
Rodrick Chapman