100

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

假设我有继承自 Animals 的 Dogs 和 Cats。我需要对狗和猫进行 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

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

有什么意见或建议吗?

4

6 回答 6

44

我会建议:

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

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

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

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

GET /animals( Accept : 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"
}

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

上述方案为您提供了与通过 REST 的 OO 继承类似的基本特征,并且能够添加进一步的专业化(即更多动物类型),而无需进行大手术或对您的 URI 方案进行任何更改。

于 2012-06-04T15:13:40.877 回答
8

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

从 JSON 模式 v1.0 开始,可以使用 oneOf、allOf、anyOf 等关键字组合模式并获得经过验证的消息有效负载。

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
于 2018-01-07T00:15:00.493 回答
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 示例应该很容易。

客户总是可以依赖那里的“名称”元素(一个通用属性)。但是根据“类型”属性,将有其他元素作为动物表示的一部分。

返回这样的列表并没有任何内在的 RESTful 或非 RESTful - REST 没有规定任何特定的格式来表示数据。它只是说数据必须具有某种表示形式,并且该表示形式的格式由媒体类型标识(在 HTTP 中是 Content-Type 标头)。

想想你的用例——你需要展示混合动物的列表吗?那么,然后返回一个混合动物数据的列表。你只需要一份狗的名单吗?好吧,列一个这样的清单。

您是否执行 /animals?type=dog 或 /dogs 与未规定任何 URL 格式的 REST 无关——这将作为 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(根据定义,它是唯一的权限)。

于 2012-06-04T15:16:13.770 回答
1

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

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

于 2012-06-04T15:42:16.493 回答
1

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

我总是可以说狗是动物,母鸡也是,但是母鸡产蛋而狗是哺乳动物,所以它不能。类似的 API

获取动物/:animalID/eggs

不一致,因为表明动物的所有亚型都可以有鸡蛋(作为 Liskov 替换的结果)。如果所有哺乳动物都以“0”响应此请求,将会有一个后备,但如果我也启用 POST 方法怎么办?我应该害怕明天我的薄饼里会有狗蛋吗?

处理这些场景的唯一方法是提供一个“超级资源”,它聚合所有可能的“派生资源”之间共享的所有子资源,然后对每个需要它的派生资源进行专门化,就像我们向下转换一个对象时一样进入 oop

获取 /animals/:animalID/sons 获取 /hens/:animalID/eggs 发布 /hens/:animalID/eggs

这里的缺点是,有人可以传递狗 ID 来引用母鸡集合的实例,但狗不是母鸡,因此如果响应是 404 或 400 并带有原因消息,则不会不正确

我错了吗?

于 2018-07-13T11:32:55.680 回答
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

...

于 2020-01-22T10:29:48.497 回答