継承を使用してRESTfulAPIをモデル化する方法は?


87

RESTful APIを介して公開する必要のあるオブジェクト階層があり、URLをどのように構造化し、何を返す必要があるのか​​わかりません。ベストプラクティスが見つかりませんでした。

動物から受け継いだ犬と猫がいるとしましょう。犬と猫のCRUD操作が必要です。また、動物全般の手術もできるようになりたいです。

私の最初のアイデアは、次のようなことをすることでした。

GET /animals        # get all animals
POST /animals       # create a dog or cat
GET /animals/123    # get animal 123

/ animalコレクションは、まったく同じ構造を持たないオブジェクト(犬と猫)を返したり受け取ったりする可能性があるため、「一貫性がない」ようになりました。異なる属性を持つオブジェクトを返すコレクションを持つことは「RESTful」と見なされますか?

別の解決策は、次のように、具体的なタイプごとにURLを作成することです。

GET /dogs       # get all dogs
POST /dogs      # create a dog
GET /dogs/123   # get dog 123

GET /cats       # get all cats
POST /cats      # create a cat
GET /cats/123   # get cat 123

しかし今、犬と猫の関係は失われています。すべての動物を回収したい場合は、犬と猫の両方のリソースを照会する必要があります。URLの数も、新しい動物のサブタイプごとに増加します。

別の提案は、これを追加することによって2番目のソリューションを拡張することでした。

GET /animals    # get common attributes of all animals

この場合、返される動物にはすべての動物に共通の属性のみが含まれ、犬固有および猫固有の属性は削除されます。これにより、詳細は少なくなりますが、すべての動物を取得できます。返される各オブジェクトには、詳細で具体的なバージョンへのリンクが含まれている可能性があります。

コメントや提案はありますか?

回答:


41

私は提案します:

  • リソースごとに1つのURIのみを使用する
  • 属性レベルでのみ動物を区別する

同じリソースに複数のURIを設定すると、混乱や予期しない副作用が発生する可能性があるため、決して良い考えではありません。そのため、単一のURIはのような一般的なスキームに基づいている必要があります/animals

「ベース」レベルで犬と猫のコレクション全体を処理するという次の課題は、/animalsURIアプローチのおかげですでに解決されています。

犬や猫などの特殊なタイプを扱う最後の課題は、メディアタイプ内のクエリパラメータと識別属性の組み合わせを使用して簡単に解決できます。例えば:

GET /animalsAccept : application/vnd.vet-services.animals+json

{
   "animals":[
      {
         "link":"/animals/3424",
         "type":"dog",
         "name":"Rex"
      },
      {
         "link":"/animals/7829",
         "type":"cat",
         "name":"Mittens"
      }
   ]
}
  • GET /animals -すべての犬と猫を取得し、レックスとミトンの両方を返します
  • GET /animals?type=dog -すべての犬を取得し、レックスのみを返します
  • GET /animals?type=cat -すべての猫を手に入れます、ミトンだけでしょう

次に、動物を作成または変更する場合、関係する動物のタイプを指定するのは発信者の責任です。

メディアタイプ: application/vnd.vet-services.animal+json

{
   "type":"dog",
   "name":"Fido"
}

上記のペイロードは、POSTまたはPUTリクエストで送信できます。

上記のスキームは、RESTを介したOO継承と同様の基本的な特性を備えており、大手術やURIスキームの変更なしに、さらに特殊化(つまり、より多くの動物タイプ)を追加する機能を備えています。


これは、RESTAPIを介した「キャスト」と非常によく似ているようです。また、C ++サブクラスのメモリレイアウトの問題/解決策を思い出させます。たとえば、メモリ内の単一のアドレスでベースクラスとサブクラスの両方を同時に表す場所と方法。
trcarden 2014年

10
私が提案するもの: GET /animals - gets all dogs and cats GET /animals/dogs - gets all dogs GET /animals/cats - gets all cats
dipold 2014

1
GETリクエストパラメータとして目的のタイプを指定することに加えて、これを実現するためにacceptタイプを使用できるように思われます。つまり: GET /animals 受け入れるapplication/vnd.vet-services.animal.dog+json
ブリアンを。

22
猫と犬がそれぞれ独自の特性を持っている場合はどうでしょうか?どのようにそれを扱うでしょうPOST、ほとんどのフレームワークは、JSONが良いタイピング情報を運ばないよう適切にモデルにそれをデシリアライズする方法を知っているだろうとして、操作。ポストケースをどのように処理し[{"type":"dog","name":"Fido","playsFetch":true},{"type":"cat","name":"Sparkles","likesToPurr":"sometimes"}ますか?
LB2 2016

1
犬と猫が(大部分の)異なる特性を持っていた場合はどうなりますか?例:SMS(to、mask)と電子メール(電子メールアドレス、cc、bcc、to、from、isHtml)の通信のPOST、またはeg#2クレジットカード(maskedPan、nameOnCard、Expiry)のFundingSourceのPOST BankAccount(bsb、accountNumber)...引き続き単一のAPIリソースを使用しますか?これは...確か、これはAPIの設計に適用される場合SOLID原理から、単一の責任に違反しているように見えるが、ないだろう
Ryan.Bartsch

5

この質問は、OpenAPIの最新バージョンで導入された最近の拡張機能のサポートにより、より適切に回答できます。

oneOf、allOf、anyOfなどのキーワードを使用してスキーマを組み合わせ、JSONスキーマv1.0以降で検証されたメッセージペイロードを取得することが可能になりました。

https://spacetelescope.github.io/understanding-json-schema/reference/combining.html

ただし、OpenAPI(以前のSwagger)では、スキーマの構成がキーワードdiscriminator(v2.0 +)およびoneOf(v3.0 +)によって拡張され、ポリモーフィズムを真にサポートしています。

https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#schemaComposition

継承は、oneOf(サブタイプの1つを選択するため)とallOf(タイプとそのサブタイプの1つを組み合わせるため)の組み合わせを使用してモデル化できます。以下は、POSTメソッドのサンプル定義です。

paths:
  /animals:
    post:
      requestBody:
      content:
        application/json:
          schema:
            oneOf:
              - $ref: '#/components/schemas/Dog'
              - $ref: '#/components/schemas/Cat'
              - $ref: '#/components/schemas/Fish'
            discriminator:
              propertyName: animal_type
     responses:
       '201':
         description: Created

components:
  schemas:
    Animal:
      type: object
      required:
        - animal_type
        - name
      properties:
        animal_type:
          type: string
        name:
          type: string
      discriminator:
        property_name: animal_type
    Dog:
      allOf:
        - $ref: "#/components/schemas/Animal"
        - type: object
          properties:
            playsFetch:
              type: string
    Cat:
      allOf:
        - $ref: "#/components/schemas/Animal"
        - type: object
          properties:
            likesToPurr:
              type: string
    Fish:
      allOf:
        - $ref: "#/components/schemas/Animal"
        - type: object
          properties:
            water-type:
              type: string

1
OASがこれを許可しているのは事実です。ただし、Swagger UI(リンク)に表示される機能はサポートされておらず、誰にも表示できない機能は使用が制限されていると思います。
emft 2018年

1
@emft、真実ではありません。この回答を書いている時点で、SwaggerUIはすでにそれをサポートしています。
Andrejs Cainikovs 2018

おかげで、これはうまくいきます!ただし、SwaggerUIがこれを完全に表示していないことは現在のところ真実のようです。モデルは下部のスキーマセクションに表示され、oneOfセクションを参照する応答セクションは部分的にUIに表示されます(スキーマのみ、例なし)が、リクエスト入力のサンプル本文は表示されません。このためのgithubの問題は3年間開いているので、そのままでいる可能性があります:github.com/swagger-api/swagger-ui/issues/3803
Jethro

4

私は/ animalsが犬と魚の両方のリストを返し、他に何でも返すようにします。

<animals>
  <animal type="dog">
    <name>Fido</name>
    <fur-color>White</fur-color>
  </animal>
  <animal type="fish">
    <name>Wanda</name>
    <water-type>Salt</water-type>
  </animal>
</animals>

同様のJSONの例を実装するのは簡単なはずです。

クライアントは、そこにある「name」要素(共通の属性)に常に依存できます。しかし、「タイプ」属性に応じて、動物表現の一部として他の要素があります。

このようなリストを返すことには、本質的にRESTfulまたはunRESTfulはありません。RESTは、データを表すための特定の形式を規定していません。データには何らかの表現が必要であり、その表現の形式はメディアタイプ(HTTPではContent-Typeヘッダー)によって識別されるということだけです。

ユースケースについて考えてください-混合動物のリストを表示する必要がありますか?それでは、混合動物データのリストを返します。犬のリストだけが必要ですか?さて、そのようなリストを作成します。

/ animal?type = dogまたは/ dogsのどちらを実行するかは、URL形式を規定しないRESTとは無関係です。これは、RESTの範囲外の実装の詳細として残されます。RESTは、リソースには識別子が必要であると述べているだけです。どの形式でもかまいません。

RESTful APIに近づくには、ハイパーメディアリンクを追加する必要があります。たとえば、動物の詳細への参照を追加することによって:

<animals>
  <animal type="dog" href="https://stackoverflow.com/animals/123">
    <name>Fido</name>
    <fur-color>White</fur-color>
  </animal>
  <animal type="fish" href="https://stackoverflow.com/animals/321">
    <name>Wanda</name>
    <water-type>Salt</water-type>
  </animal>
</animals>

ハイパーメディアリンクを追加することで、クライアント/サーバーの結合を減らすことができます。上記の場合、URL構築の負担をクライアントから取り除き、サーバーにURLの構築方法を決定させます(定義上、これが唯一の権限です)。


1

しかし今、犬と猫の関係は失われています。

ただし、URIがオブジェクト間の関係を反映することは決してないことに注意してください。


1

これは古い質問ですが、RESTful継承モデリングに関するさらなる問題を調査することに興味があります

犬は動物であり鶏でもあるとは言えますが、犬は哺乳類であるのに対し、鶏は卵を産むのでできません。のようなAPI

GETanimals /:animalID / eggs

動物のすべてのサブタイプが卵を持つことができることを示しているため、一貫性がありません(リスコフ置換の結果として)。すべての哺乳類がこのリクエストに「0」で応答するとフォールバックが発生しますが、POSTメソッドも有効にするとどうなりますか?明日はクレープに犬の卵が入るのではないかと心配する必要がありますか?

これらのシナリオを処理する唯一の方法は、オブジェクトをダウンキャストするときと同じように、すべての可能な「派生リソース」間で共有されるすべてのサブリソースを集約する「スーパーリソース」を提供し、それを必要とする各派生リソースの特殊化を提供することです。おっとに

GET / animal /:animalID / sons GET / hens /:animalID / eggs POST / hens /:animalID / eggs

ここでの欠点は、誰かが犬のIDを渡して鶏のコレクションのインスタンスを参照できることですが、犬は鶏ではないため、応答が404または400で、理由メッセージが表示されても間違いではありません。

私が間違っている?


1
URI構造を重視しすぎていると思います。「animals /:animalID / eggs」にアクセスできる唯一の方法はHATEOASを使用することです。したがって、最初に「animals /:animalID」を介して動物をリクエストし、次に卵を持つことができる動物については「animals /:animalID / eggs」へのリンクがあり、そうでない動物についてはリンクがありません。動物から卵へのリンクになります。誰かがどういうわけか卵を産むことができない動物の卵に
行き着いた

0

はい、あなたは間違っています。また、関係は、OpenAPI仕様に従って、たとえばこの多態的な方法でモデル化できます。

Chicken:
  type: object
  discriminator:
    propertyName: typeInformation
  allOf:
    - $ref:'#components/schemas/Chicken'
    - type: object
      properties:
        eggs:
          type: array
          items:
            $ref:'#/components/schemas/Egg'
          name:
            type: string

..。


追加コメント:APIルートのフォーカスは GET chicken/eggs 、コントローラー用の一般的なOpenAPIコードジェネレーターを使用しても機能するはずですが、これはまだ確認していません。多分誰かが試すことができますか?
アンドレアスガウス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.