用户自定义资源的双资源设计是否符合RESTful API规范?
在我们的项目中,需要表示用户自定义的资源——每个用户可以拥有字段、验证规则各不相同的资源。因此我们的API需要处理两类内容:
- 资源定义(Resource definition):和JSON Schema非常相似,包含资源的字段定义及限制规则(比如数值字段的最小/最大值)。例如下面是
Person的资源定义:
{ "$id": "https://example.com/person.schema.json", "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Person", "type": "object", "properties": { "firstName": { "type": "string", "description": "The person's first name." }, "lastName": { "type": "string", "description": "The person's last name." }, "age": { "description": "Age in years which must be equal to or greater than zero.", "type": "integer", "minimum": 0 } } }
- 资源实例(Resource instance):是指定资源的具体实例。比如针对上面的
Person资源定义,我们可以有以下实例:
[ { "firstName": "Elena", "lastName": "Gomez" }, { "firstName": "Elena2", "lastName": "Gomez2" } ]
这种设计似乎和RESTful API的设计理念存在冲突,尤其是在**统一接口(Uniform Interface)**这一点上有问题。按照REST的思路,获取资源时应该不需要额外信息就能处理该资源,但当前设计下必须先发起额外请求获取资源定义才能处理。举个例子:
假设你是我们的Web客户端,以拥有Person资源的用户身份登录。要在UI中展示Person数据,你得先了解Person的结构,也就是先发起请求:GET /resource_definitions/person,之后才能请求具体的Person对象:GET /resource/person/123。
另一些人认为这不算问题,当前设计仍然符合RESTful规范。调用API本来就需要预先知晓数据格式,API本身不需要提供自文档,所以这类端点的行为和其他端点保持一致是合理的。
那么这个方案到底符不符合RESTful API的设计规范?
这个方案完全符合RESTful API设计规范,理由如下:
统一接口的核心是约束交互方式,而非要求资源自带元数据
REST的统一接口约束指的是使用标准的HTTP方法(GET/POST/PUT/DELETE等)、标准化的状态码、自描述消息等,并不要求资源实例必须包含自身的结构定义。你需要先获取资源定义再处理实例的情况,本质上是客户端需要元数据来解析资源,这和REST的核心约束不冲突——REST并没有禁止客户端依赖额外的元数据资源。REST允许客户端通过超媒体发现资源关系
你可以把资源定义的链接嵌入到资源实例的响应中,比如在GET /resource/person/123的响应头部或body里加入指向/resource_definitions/person的链接,这样客户端就能通过超媒体自动发现元数据的位置,这反而更符合REST的“超媒体作为应用状态引擎(HATEOAS)”原则。预先知晓格式是API交互的常态
观点二提到的“调用API需预先知晓格式”是实际场景中的普遍情况——绝大多数API的客户端都是预先知道资源结构的,只是你的场景中结构是用户自定义的,所以需要动态获取元数据,但这并不违反REST的规范,只是把静态的文档变成了可动态获取的资源而已。
总结来说,只要你的API遵循了HTTP方法的语义、使用标准状态码、提供清晰的资源标识(URI),即使需要分开获取资源定义和实例,也完全符合RESTful的设计要求。
内容的提问来源于stack exchange,提问作者Antonio Gamiz Delgado

