原文地址:https://blog.philipphauer.de/restful-api-design-best-practices/
项目资源的URL应该如何设计?用名词复数还是用名词单数?一个资源需要多少个URL?用哪种HTTP方法来创建一个新的资源?可选参数应该放在哪里?那些不涉及资源操作的URL呢?实现分页和版本控制的最好方法是什么?因为有太多的疑问,设计RESTful API变得很棘手。在这篇文章中,我们来看一下RESTful API设计,并给出一个最佳实践方案。
每个资源使用两个 URL
资源集合用一个URL,具体某个资源用一个URL:
/employees #资源集合的URL
/employees/56 #具体某个资源的URL
用名词代替动词表示资源
让你的API更简洁,URL数目更少。
/getAllEmployees
/getAllExternalEmployees
/createEmployee
/updateEmployee
推荐设计:
GET /employees
GET /employees?state=external
POST /employees
PUT /employees/1
用 HTTP 方法操作资源
使用 URL 确定你要用的资源,用 HTTP 方法来指定怎么处理这个资源。四种 HTTP 方法 POST,GET,PUT,DELETE 来提供 CRUD 功能(创建、获取、更新、删除)。
- 获取:使用GET方法获取资源。GET请求从不改变资源的状态;GET方法是幂等的;GET方法具有只读的含义,因此你可以完美的使用缓存。
- 创建:使用POST创建新的资源。
- 更新:使用PUT更新现有资源。
- 删除:使用DELETE删除现有资源。
2个 URL 乘以4个 HTTP 方法的方法矩阵提供完整的功能:
POST(创建) | GET(读取) | PUT(更新) | DELETE(删除) | |
---|---|---|---|---|
/employees | 创建一个新员工 | 获取所有员工 | 批量更新员工信息 | 删除所有员工 |
/employees/1 | 错误 | 获取ID=1员工信息 | 更新ID=1员工信息 | 删除ID=1员工 |
创建新资源,对资源集合的 URL 使用 POST 方法
创建一个新资源的时,客户端与服务器是怎么交互的呢?
在资源集合URL上使用POST来创建新的资源过程
- 客户端向资源集合 URL
/employees
发送 POST 请求。HTTP body 包含新资源的属性 “Albert Stark”。 - RESTful Web 服务器为新员工生成ID,在其内部模型中创建员工,并向客户端发送响应。这个响应的HTTP头部包含一个 Location 字段,指示创建资源可访问的URL。
更新资源,对具体资源的 URL 使用 PUT 方法
- 客户端向具体资源的 URL 发送 PUT 请求
/employee/21
。请求的HTTP body中包含要更新的属性值(21号员工的新名称“Bruce Wayne”)。 - REST 服务器更新ID为21的员工名称,并使用HTTP状态码200表示更改成功。
URL 推荐用复数名词
推荐:
/employees
/employees/21
不推荐:
/employee
/employee/21
这是个人爱好问题,但复数形式更为常见。此外,在资源集合 URL 上用 GET 方法,它更直观,特别是GET /employees?state=external
、POST /employees
、PUT /employees/56
注意:避免复数和单数名词混合使用,这样非常混乱且容易出错。
URL 对可选的、复杂的参数,使用查询字符串(?)。
不推荐做法:
GET /employees
GET /externalEmployees
GET /internalEmployees
GET /internalAndSeniorEmployees
为了让 URL 更小、更简洁。为资源设置一个基本 URL,将可选的、复杂的参数用查询字符串表示。
GET /employees?state=internal&maturity=senior
使用HTTP状态码
RESTful Web 服务应使用合适的 HTTP 状态码来响应客户端请求
- 2xx : 成功 – 一切正常
- 4xx : 客户端错误 – 如果客户端发生错误(例如客户端发送无效请求或未被授权)
- 5xx : 服务器错误 – 如果服务器发生错误(例如,尝试处理请求时出错)
详细参考 维基百科 或 百度百科 中的 HTTP 状态代码。其中的大部分 HTTP 状态码都不会被用到,通常会用到一下几种:
2xx:成功 | 3xx:重定向 | 4xx:客户端错误 | 5xx:服务器错误 |
---|---|---|---|
200 成功 | 301 永久重定向 | 400 请求错误 | 500 服务器内部错误 |
201 创建成功 | 304 资源未修改 | 401 未授权 | |
403 禁止访问 | |||
404 未找到 |
错误请求,返回有用的错误提示
除了返回状态码之外,还应该在HTTP响应正文中提供有用的错误提示和详细的描述。比如:
请求:
GET /employees?state=super
响应:
// 400 Bad Request
{
"message": "You submitted an invalid state. Valid state values are 'internal' or 'external'",
"errorCode": 352,
"additionalInformation" :
"http://www.domain.com/rest/errorcode/352"
}
URL 使用小驼峰命名法
使用小驼峰命名法作为属性标识符。
{ "yearOfBirth": 1982 }
不要使用下划线(year_of_birth)或大驼峰命名法(YearOfBirth)。通常 RESTful Web服务将被JavaScript编写的客户端使用。客户端会将JSON响应转换为JavaScript对象(通过调用var person = JSON.parse(response)),然后调用其属性。因此,最好遵循JavaScript代码通用规范。
对比:
person.year_of_birth // 不推荐,违反JavaScript代码通用规范
person.YearOfBirth // 不推荐,JavaScript构造方法命名
person.yearOfBirth // 推荐
在 URL 中强制加入版本号
从始至终,都使用版本号发布您的 RESTful API。将版本号放在URL中以是必需的。如果您有不兼容和破坏性的更改,版本号将让你能更容易的发布API。发布新API时,只需在增加版本号中的数字。这样的话客户端可以自如的迁移到新API,不会因调用完全不同的新 API 而陷入困境。
使用直观的 “v” 前缀来表示后面的数字是版本号。
/v1/employees
你不需要使用次级版本号(“v1.2”),因为你不应该频繁的去发布API版本。
提供分页信息
/employees?offset=30&limit=15 #返回30 到 45的员工
如果客户端没有传这些参数,则应使用默认值。通常默认值是 offset = 0 和 limit = 10。如果数据库检索很慢,应当减小limit值。
/employees #返回0 到 10的员工
此外,如果您使用分页,客户端需要知道资源总数。例:
请求:
GET /employees
响应:
{
"offset": 0,
"limit": 10,
"total": 3465,
"employees": [
//...
]
}
非资源请求用动词
有时 API 调用并不涉及资源(如计算,翻译或转换)。例:
GET /translate?from=de_DE&to=en_US&text=Hallo
GET /calculate?para2=233&p3=432
在这种情况下,API 响应不会返回任何资源。而是执行一个操作并将结果返回给客户端。因此,您应该在URL中使用动词而不是名词,来清楚的区分资源请求和非资源请求。
考虑特定资源搜索和跨资源搜索
提供对特定资源的搜索很容易。只需使用相应的资源集合URL,并将搜索字符串附加到查询参数中即可。
GET /employees?query=Paul
如果要对所有资源提供全局搜索,则需要用其他方法。前文提到,对于非资源请求URL,使用动词而不是名词。因此,您的搜索网址可能如下所示:
GET /search?query=Paul //返回 employees, customers, suppliers 等等.
在响应参数中添加浏览其它 API 的链接
理想情况下,不会让客户端自己构造使用 REST API 的 URL。比如,客户端想要访问员工的薪酬表。为此,他必须知道他可以通过在员工 URL(/employees/21/salaryStatements
)中附加字符串“salaryStatements”来访问薪酬表。这个字符串连接很容易出错,且难以维护。如果你更改了访问薪水表的 REST API的方式(/employees/21/salary-statement
或 /employees/21/paySlips
),所有客户端都将中断。 更好的方案是在响应参数中添加一个links字段,让客户端可以自动变更。
请求:
GET /employees/
响应:
//...
{
"id":1,
"name":"Paul",
"links": [
{
"rel": "salary",
"href": "/employees/1/salaryStatements"
}
]
},
//...
如果客户端完全依靠 links 中的字段获得薪资表,你更改了API,客户端将始终获得一个有效的 URL(只要你更改了link字段,请求的 URL 会自动更改),不会中断。另一个好处是,你的API变得可以自我描述,需要写的文档更少。
在分页时,您也可以添加获取下一页或上一页的链接示例。只需提供适当的偏移和限制的链接示例。
GET /employees?offset=20&limit=10
响应:
{
"offset": 20,
"limit": 10,
"total": 3465,
"employees": [
//...
],
"links": [
{
"rel": "nextPage",
"href": "/employees?offset=30&limit=10"
},
{
"rel": "previousPage",
"href": "/employees?offset=10&limit=10"
}
]
}