如何使用继承建模RESTful API?

时间:2012-06-04 11:26:16

标签: api rest inheritance polymorphism

我有一个我需要通过RESTful API公开的对象层次结构,我不确定我的URL应该如何构建以及它们应该返回什么。我找不到任何最佳做法。

假设我有继承动物的狗和猫。我需要对狗和猫进行CRUD操作;我也希望能够对动物进行一般的操作。

我的第一个想法是做这样的事情:

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

事情是/ animals集合现在“不一致”,因为它可以返回并获取不具有完全相同结构的对象(狗和猫)。将集合返回具有不同属性的对象,它被认为是“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数量也会增加。

另一个建议是通过添加以下内容来扩充第二个解决方案:

GET /animals    # get common attributes of all animals

在这种情况下,返回的动物将只包含所有动物共有的属性,丢弃特定于狗的属性和特定于猫的属性。这允许检索所有动物,尽管细节较少。每个返回的对象都可以包含指向详细具体版本的链接。

有任何意见或建议吗?

6 个答案:

答案 0 :(得分:36)

我建议:

  • 每个资源只使用一个URI
  • 仅在属性级别区分动物

为同一资源设置多个URI绝不是一个好主意,因为它可能会导致混淆和意外的副作用。鉴于此,您的单个URI应基于通用方案,如/animals

在“基础”级别处理整个狗和猫的集合的下一个挑战已经通过/animals URI方法解决了。

使用媒体类型中的查询参数和标识属性的组合,可以轻松解决处理狗和猫等特殊类型的最终挑战。例如:

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"
}

上述有效负载可以通过POSTPUT请求发送。

上述方案通过REST获得与OO继承相似的基本类似特征,并且能够在不进行大型手术或对URI方案进行任何更改的情况下添加更多特化(即更多动物类型)。

答案 1 :(得分:4)

我会去/动物返回一份狗和鱼的清单以及其他任何东西:

<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标题)。

考虑一下您的用例 - 您是否需要显示混合动物列表?好吧,然后返回混合动物数据列表。你只需要一份狗列表吗?好吧,制作这样的清单。

你是否/动物?type = dog或/ dogs与REST无关,它没有规定任何URL格式 - 这是作为REST范围之外的实现细节。 REST仅声明资源应该具有标识符 - 不管哪种格式。

您应该添加一些超媒体链接以更接近RESTful API。例如,通过添加对动物细节的引用:

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

通过添加超媒体链接,减少客户端/服务器耦合 - 在上述情况下,您将URL构造的负担远离客户端,让服务器决定如何构造URL(根据定义,它是唯一的权限)

答案 2 :(得分:2)

在最新版本的OpenAPI中引入的最新增强功能的支持下,可以更好地回答这个问题。

可以使用关键字(如oneOf,allOf,anyOf)组合模式,并获取自JSON模式v1.0以来验证的消息有效内容。

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

然而,在OpenAPI(前Swagger)中,关键字鉴别器(v2.0 +)和 oneOf (v3.0 +)增强了架构组合真正支持多态性。

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

您的继承可以使用oneOf(用于选择其中一个子类型)和allOf(用于组合类型及其一个子类型)的组合进行建模。 以下是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

答案 3 :(得分:1)

  

但现在狗狗之间的关系已经失去了。

确实,但请记住,URI根本不会反映对象之间的关系。

答案 4 :(得分:1)

我知道这是一个老问题,但是我有兴趣研究有关RESTful继承模型的更多问题

我总是可以说狗是动物,母鸡也是,但是母鸡会产卵,而狗是哺乳动物,所以不会。像

这样的API

获取动物/:animalID /鸡蛋

是不一致的,因为它表示动物的所有亚型都可以有卵(由于Liskov替代)。如果所有的哺乳动物都对此请求做出“ 0”响应,将会有一个回退,但是如果我还启用了POST方法,该怎么办?我应该担心明天薄饼里会有狗蛋吗?

处理这些情况的唯一方法是提供一个“超级资源”,该资源聚合所有可能的“派生资源”之间共享的所有子资源,然后为需要它的每个派生资源专门化,就像我们将对象贬低到oop

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

此处的缺点是,有人可以传递狗ID来引用母鸡集合实例,但该狗不是母鸡,因此如果响应为404或400,并带有原因消息,则它不会是错误的< / p>

我错了吗?

答案 5 :(得分: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

...