我有一个我需要通过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
在这种情况下,返回的动物将只包含所有动物共有的属性,丢弃特定于狗的属性和特定于猫的属性。这允许检索所有动物,尽管细节较少。每个返回的对象都可以包含指向详细具体版本的链接。
有任何意见或建议吗?
答案 0 :(得分:36)
我建议:
为同一资源设置多个URI绝不是一个好主意,因为它可能会导致混淆和意外的副作用。鉴于此,您的单个URI应基于通用方案,如/animals
。
在“基础”级别处理整个狗和猫的集合的下一个挑战已经通过/animals
URI方法解决了。
使用媒体类型中的查询参数和标识属性的组合,可以轻松解决处理狗和猫等特殊类型的最终挑战。例如:
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"
}
上述有效负载可以通过POST
或PUT
请求发送。
上述方案通过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
...