APIStyleGuide
目录
OpenStack APIs REST 风格指南
本文档解答关于 REST API 的问题,并为 OpenStack API 项目提供风格指导和最佳实践,以确保我们的 API 保持一致性。
REST(表述性状态转移)是由 Roy Fielding 在他的博士论文中形式化的一种分布式计算风格。Fielding 研究了 Web 的架构成功,并将他的发现概括为一组可应用于其他分布式超媒体系统的设计原则。
REST 提出了这些原则
- 可以使用 URI 来寻址每个资源。
- 通过它们的表示来操作资源。例如,XML、HTML、JSON、YAML 等。
- 为所有资源公开统一的接口。例如,GET、PUT、POST、DELETE 等。
- 消息是自描述的。每个消息包含无状态服务器处理它所需的一切
- 超媒体作为应用程序状态的引擎 (HATEOAS)。资源的超媒体表示本身驱动状态转换,例如更改资源的状态或检查相关资源。
将这些原则应用于 Web 服务的好处包括水平可扩展性、解耦的客户端和服务器,以及通过使用代理和真正的互操作性来减少延迟。
RESTful API 使用起来很简单。例如,您可以使用 cURL 将一个简单的客户端指向 API 的 URI,并快速构建一些有用的东西。
这是一个 wiki,所以请随意编辑它。
指南
这些指南帮助 API 设计者创建风格一致的云 API。
何时更改 API
有关何时可以更改 API 的信息,请参阅 API 变更指南。
XML 风格
对于 XML 元素和属性名称,请使用 python 编码风格
lowercase_underscore
示例
<customer_order product_type="book">
<book href="..."/> ...
</customer_order>
URL 风格
URL 应该全部小写,不含标点符号,例如:
http://foo.com/dublintours/guinnessstorehouse/
除了查询参数,查询参数应该小写
http://foo.com/dublintours/guinnessstorehouse/?start_date=today
集合
URL 和 XML 集合的元素是资源名称的复数形式,例如:
<departments> </department> </department> <department>
入口点
顾名思义,HATEAOS,每个 API 都应努力实现单个入口点 URI。
如果存在由 API 表示的自然顶级对象或集合,则该对象或集合可以由入口点寻址的资源。该资源将具有链接,客户端可以使用这些链接导航到所有其他资源。
在其他情况下,顶级 URI 应该只是返回一个“api”资源,其中包含指向 API 中其他资源或资源集合的链接,例如:
GET / HTTP/1.1 Host: {host}
HTTP/1.1 200 OK Content-Type: application/xml Content-Length: {length} <api>
<link rel="books" href="/books"/> <link rel="music" href="/music"/> <link rel="orders" href="/orders"/>
</api>
客户端使用其对 API 链接关系类型的了解来确定它需要的 URI。
XML schema
RESTful 服务应该是面向表示的。这意味着您应该密切关注服务的资源表示。
我们 API 中资源的默认表示形式是 XML。每个 API 都应该有一个 XML schema(或 RNG schema),客户端和服务器都可以使用它来验证其 XML 输出在他们的测试套件中。部署的服务器不应使用此 schema 验证客户端输入,以便较新的客户端可以继续与较旧的服务器一起工作。
在 Java 服务器使用 JAX-RS 的情况下,从表示的 schema 开始,然后使用 xjc 生成 JAX-B 注释类是有意义的。这确保了您的设计重点是 XML 表示形式,而不是代码中的对象模型。
JSON
描述我们 XML 到 JSON 的映射。
参考文献
Some rest-practices discussion
YAML
描述我们 YAML 到 JSON 的映射。兼容性和版本控制
一旦发布,所有云 API 都应做出 API 稳定性保证。资源表示可以扩展,但必须以向后兼容的方式进行 - 即旧客户端可以与新服务器一起工作。
如果 API 演进的任何时候必须进行向后不兼容的更改,则应添加一个新的链接关系类型来支持新的不兼容表示形式,并保留旧的关系类型,例如:
<api>
<link rel="books_v2" href="/books/v2/"/> <link rel="books" href="/books"/> <link rel="music" href="/music"/> <link rel="orders" href="/orders"/>
</api>
FIXME:讨论基于版本化媒体类型使用内容类型协商的选项。文档
API 应该记录。建议一种文档风格。
标识符
资源通常可能具有三种类型的标识符与之关联
An opaque, server-generated identifier like a UUID. This identifier should be relatively permanent and suitable for clients to store in their own database. Give an entry point URI and this identifier, the client should be able to navigate to the resource. A URI, which is also opaque and server-generated, but less stable. The server hostname may change, the API entry point may move or the URI structure may change. For these reasons, clients should only use URIs during a single session. An optional human-readable name, most likely assigned by the user. It should be possible to find the resource using its name, but clients should be aware that the name can be changed at any time by the user.
一些指南自然地从这些观察中得出
When a server response includes a reference to a resource, the most natural identifier to supply is the URI. However, the primary id could also be supplied for convenience e.g.
GET /groups/ HTTP/1.1 <group id="666" href="/groups/666">
... <members> <user id="101" href="/users/101"/> <user id="202" href="/users/202"/> </members>
</group>
When a client must supply a reference to a resource, it should only be required to supply the primary ID e.g.
POST /vms/ HTTP/1.1 <vm>
... <template id="67e2aa74-2b84-4d50-96e2-d1ec5b961c24">
</vm>
参考文献
Some rest-practices discussion
链接
资源通常需要引用其他资源。一种选择是使用被引用资源的局部表示形式,例如:
<user href="/users/101"/>
或者
<order id="364782"/>
另一种选择是使用 Atom 链接
<actions>
<link rel="reboot" href="/vms/1234/reboot"/> <link rel="shutdown" href="/vms/1234/shutdown"/>
</actions>
这两种选择都有其用途,因此请根据这些指南决定哪种最合适
If the referenced resource is one of the main objects in the API, then <resource href="..."/> is probably appropriate If the reference is to one of a number of resources of the same and the Atom link's relationship tag could be used to allow the client to pick between them, then use an Atom link Prefer a href attribute over a <link rel="self" href="..."/> link If you want to also expose the same link in the HTTP headers, then perhaps use an Atom link in the body for consistency
参考文献
RFC4287 section on atom:link
Link Headers
使用 Link headers 暴露与资源相关的某些链接到 HTTP headers 可能会很有用。这允许客户端在不解析响应主体的情况下获得这些链接,并且可以使用 HEAD 请求来避免获取主体。
但是,它们仅在实体主体只能包含给定关系类型的一个链接时才适用 - 例如,它可能不适用于包含多个对象的响应。
此外,某些客户端和服务器难以处理相同名称的多个 headers。为了避免此问题,请使用逗号分隔将多个 Link headers 串联起来。
HTTP Link header 规范草案要求通过 URI 定义应用程序定义的任何自定义关系。这不方便,也不是常见的做法,也没有完全获得批准,因此我们现在避免使用它。
参考文献
Internet-Draft for Link headers On concatenating multiple link headers “rel” name requirement overloaded (bburke blog)
URI Templates
当在 API 中使用查询参数时,使用 URI templates 可以避免将有关 URI 结构的详细知识泄露给客户端。
API 应该记录它将使用的 URI template 规范的子集以及将在 API 的 templates 中使用的替换变量。
URI Templates 规范可在 https://github.com/uri-templates/uritemplate-spec 找到,其中包含实现列表 https://github.com/uri-templates/uritemplate-spec/wiki/Implementations 和一组用于实现的测试 https://github.com/uri-templates/uritemplate-test,应该参考这些内容。
参考文献
URI templates Internet-Draft: http://tools.ietf.org/html/rfc6570 rest-practices discussion
媒体类型
在 headers 中编码更多关于应用程序协议的详细信息,而不是在请求/响应主体中。
如果使用媒体类型,我们应该继续支持 application/xml、application/json、application/x-yaml。
使用媒体类型进行版本控制?
媒体类型的范围?请查看例如 Sun Cloud API 的一组媒体类型。
我们媒体类型的命名。
使用 +json 和 +yaml 修饰符。
参考文献
rest-practices thread RFC3023, XML Media Types
只读字段
Ignore changes to read-only fields Return an error if PUT/POSTed doc includes a read-only field Return an error if PUT/POSTed doc includes a change to a read-only field
(1) 意味着我们没有明确语义。
(2) 对希望使用 xpath 进行小幅更改然后 PUT 结果的客户端来说限制过于严格。
(3) 有效,但对于经常更改的只读值(例如可用磁盘空间)而言。
我们务实的做法结合了 (1) 和 (3)。
参考文献
rest practices thread
查询参数
允许服务器指示客户端如何构造适当的 URI,例如在 HTML 表单和 URI templates 中所做的那样,通过在媒体类型和链接关系中定义这些说明?
CRUD
基本的 CRUD(创建、读取、更新、删除)操作应该使用 HTTP POST、GET、PUT 和 DELETE 方法建模。
为了列出集合的内容,您 GET 集合 URI
GET /resources/ HTTP/1.1
HTTP/1.1 200 OK Content-Type: application/xml
<resources>
<resource id="12345" href="/resources/12345"> <name>foo</name> ... </resource> <resource ...> ... </resource>
</resources>
请注意,服务器可以选择在列出集合时返回资源的局部表示形式。客户端应该依赖于所有可用的都是单个资源 URI。
要获取单个资源的完整表示形式,您 GET 资源 URI
GET /resources/12345 HTTP/1.1
HTTP/1.1 200 OK Content-Type: application/xml
<resource id="12345" href="/resources/12345">
...
</resource>
要创建新资源,您 POST 到集合 URI。新创建资源的 URI 在 Location header 中返回,并且可选地,资源的表示形式在响应主体中返回
POST /resources HTTP/1.1 Content-Type: application/xml
<resource>
<name>foo</name>
</resource>
HTTP/1.1 201 Created Location: /resources/54321 Content-Type: application/xml Content-Length: <length>
<resource id="54321" href="/resources/54321">
<name>foo</name> ...
</resource>
要修改资源,您 PUT 到资源 URI
PUT /resources/54321 HTTP/1.1 Content-Type: application/xml
<resource>
<name>bar</name>
</resource>
HTTP/1.1 200 OK Content-Type: application/xml
<resource id="54321" href="/resources/54321">
<name>bar</name> ...
</resource>
最后,要删除资源,您 DELETE 资源 URI
DELETE /resources/54321 HTTP/1.1
HTTP/1.1 204 No Content
建模操作
参考文献
Modelling operations in REST (bburke)
异步操作
参考文献
rest-practices thread
缓存
更新监控
错误
WADL
安全
这里有很多主题 - 用户、角色、组、身份验证、授权、加密 ...
给定用户看到的资源表示形式应取决于用户具有的权限。一些例子
There should be no 'reboot' action link in a VM representation if the user does not have permission to reboot that VM There should be no 'HR' department included in the departments collection if that user does not have permission to view the 'HR' department
语言/平台考虑因素
我们的大多数 API 使用以下方式实现
Java, JBoss, JAX-RS, RESTeasy Ruby, Rails, Sinatra, ActiveResource, ...
REST 使互操作性问题大多变得简单明了,但我们应该注意不要对某些核心客户端造成太大的不便
Shell script using e.g. curl Java (which client API do we recommend) Ruby Python
Java
RESTeasy 的 Client Proxy Framework 的优点是服务器端 JAX-RS 注释接口可以重用,但这同时也带来了一个问题。通过使用代理框架,客户端嵌入了有关 URI 结构的知识,因此容易受到 URI 结构未来更改的影响。因此,除非是快速且不担心 API 未来更改的客户端,否则不建议使用此框架。