前后端分离和微服务成为现代软件开发的大趋势下,API设计也应该变得越来越规范和高效。
1. RESTful
REST(英文:Representational State Transfer,简称REST),RESTful是一种对基于HTTP的应用设计风格,只是提供了一组设计原则和约束条件,而不是一种标准。
从本质上理解RESTful,它其实是尽可能复用HTTP特性来规范软件设计,甚至提高传输效率。HTTP包处于网络应用层,因此HTTP包为平台无关的字符串表示,如果尽可能的使用HTTP的包特征而不是大量在body定义自己的规则,可以用更简洁、清晰、高效的方式实现同样的需求。
请求方法 | 资源访问路径 | 数据操作说明 |
---|---|---|
GET | /users/ | 获取所有用户数据 |
GET | /users/1 | 获取id=1的用户数据 |
POST | /users | 新增一条用户数据 |
DELETE | /users/1 | 删除id=1的用户数据 |
RESTful的本质是基于HTTP协议对资源的增删改查操作做出定义。
1.1 几个典型的RESTful API场景:
功能 | URL | HTTP Method |
---|---|---|
获取一组数据列表 | /base-path/records | GET |
根据ID获取某个数据 | /base-path/records/{recordID} | GET |
新建数据 | /base-path/records | POST |
完整地更新数据 | /base-path/records/{recordID} | PUT |
部分更新数据 | /base-path/records/{recordID} | PATCH |
删除 | /base-path/records/{recordID} | DELETE |
夸域访问预请求 | /base-path/records/{recordID} | OPTION |
虽然HTTP协议定义了其他的Method,但是就普通场景来说,用好上面的几项已经足够了。
RESTful的几个注意点:
URL只是表达被操作的资源位置,因此不应该使用动词,且注意单复数区分
除了POST和DELETE之外,其他的操作需要冥等的,例如对数据多次更新应该返回同样的内容
设计风格没有对错之分,RESTful一种设计风格,与此对应的还有RPC甚至自定义的风格
RESTful和语言、传输格式无关
无状态,HTTP设计本来就是没有状态的,之所以看起来有状态因为我们浏览器使用了Cookies,每次请求都会把Session ID(可以看做身份标识)传递到headers中。关于RESTful风格下怎么做用户身份认证我们会在后面讲到。
RESTful没有定义body中内容传输的格式,有另外的规范来描述怎么设计body的数据结构,网络上有些文章对RESTful的范围理解有差异
1.2 避免多级 URL
常见的情况是,资源需要多级分类,因此很容易写出多级的 URL,比如获取某个作者的某一类文章。
1 | GET /authors/12/categories/2 |
这种 URL 不利于扩展,语义也不明确,往往要想一会,才能明白含义。
更好的做法是,除了第一级,其他级别都用查询字符串表达。
1 | GET /authors/12?categories=2 |
下面是另一个例子,查询已发布的文章。你可能会设计成下面的 URL。
1 | GET /articles/published |
查询字符串的写法明显更好。
1 | GET /articles?published=true |
2. JSON API
因为RESTful风格仅仅规定了URL和HTTP Method的使用,并没有定义body中数据格式的。我们怎么定义请求或者返回对象的结构,以及该如何针对不同的情况返回不同的HTTP 状态码?
API 返回的数据格式,不应该是纯文本,而应该是一个 JSON 对象,因为这样才能返回标准的结构化数据。因此,服务器回应的 HTTP 头的Content-Type属性要设为application/json。
2.1 MIME 类型
JSON API数据格式已经被IANA机构接受了注册,因此必须使用application/vnd.api+json类型。因此,客户端请求时,必须要明确告诉服务器,可以接受 JSON 格式,即请求的 HTTP 头的ACCEPT属性也要设成application/json。
1 | GET /orders/2 HTTP/1.1 |
2.2 JSON文档结构
在顶级节点使用data、errors、meta,来描述数据、错误信息、元信息。
注意:data和errors应该互斥,不能再一个文档中同时存在,meta在项目实际上用的很少,只有特别情况才需要用到,比如返回服务器的一些信息。
2.3 data属性
一个典型的data的对象格式,我们的有效信息一般都放在attributes中。
id显而易见为唯一标识,可以为数字也可以为hash字符串,取决于后端实现
type 描述数据的类型,可以对应为数据模型的类名
attributes 代表资源的具体数据
relationships、links为可选属性,用来放置关联数据和资源地址等数据
2.4 errors属性
这里的errors和data有一点不同,一般来说返回值中errors作为列表存在,因为针对每个资源可能出现多个错误信息。最典型的例子为,我们请求的对象中某些字段不符合验证要求,这里需要返回验证信息,但是HTTP状态码会使用一个通用的401,然后把具体的验证信息在errors给出来。
在title字段中给出错误信息,如果我们在本地或者开发环境想打出更多的调试堆栈信息,我们可以增加一个detail字段让调试更加方便。需要注意的一点是,我们应该在生产环境屏蔽部分敏感信息,detail字段最好在生产环境不可见。
3 常用的返回码
- 不要发生了错误但给2xx响应,客户端可能会缓存成功的http请求;
- 正确设置http状态码,不要自定义;
- Response body 提供 1) 错误的代码(日志/问题追查);2) 错误的描述文本(展示给用户)。
常用的http状态码及使用场景:
状态码 | 使用场景 |
---|---|
200 ok | 请求成功 |
201 created | POST创建资源返回成功的标志 |
400 bad request | 常用在参数校验 |
401 unauthorized | 未经验证的用户,常见于未登录。如果经过验证后依然没权限,应该 403(即 authentication 和 authorization 的区别)。 |
403 forbidden | 无权限 |
404 not found | 资源不存在 |
500 internal server error | 非业务类异常 |
503 service unavaliable | 由容器抛出,自己的代码不要抛这个异常 |
参考文献: