原文:RESTful API Design. Best Practices in a Nutshell.
做者:Philipp Hauerjava
项目资源的URL应该如何设计?用名词复数仍是用名词单数?一个资源须要多少个URL?用哪一种HTTP方法来建立一个新的资源?可选参数应该放在哪里?那些不涉及资源操做的URL呢?实现分页和版本控制的最好方法是什么?由于有太多的疑问,设计RESTful API变得很棘手。在这篇文章中,咱们来看一下RESTful API设计,并给出一个最佳实践方案。shell
资源集合用一个URL,具体某个资源用一个URL:数据库
/employees #资源集合的URL /employees/56 #具体某个资源的URL
这让你的API更简洁,URL数目更少。不要这么设计:api
/getAllEmployees /getAllExternalEmployees /createEmployee /updateEmployee
更好的设计:缓存
GET /employees GET /employees?state=external POST /employees PUT /employees/56
使用URL指定你要用的资源。使用HTTP方法来指定怎么处理这个资源。使用四种HTTP方法POST,GET,PUT,DELETE能够提供CRUD功能(建立,获取,更新,删除)。服务器
2个URL乘以4个HTTP方法就是一组很好的功能。看看这个表格:restful
POST(建立) | GET(读取) | PUT(更新) | DELETE(删除) | |
---|---|---|---|---|
/employees | 建立一个新员工 | 列出全部员工 | 批量更新员工信息 | 删除全部员工 |
/employees/56 | (错误) | 获取56号员工的信息 | 更新56号员工的信息 | 删除56号员工 |
建立一个新资源的时,客户端与服务器是怎么交互的呢?dom
在资源集合URL上使用POST来建立新的资源过程:测试
/employees
发送POST请求。HTTP body 包含新资源的属性 “Albert Stark”。使用PUT更新已有资源:spa
/employee/21
。请求的HTTP body中包含要更新的属性值(21号员工的新名称“Bruce Wayne”)。推荐:
/employees /employees/21
不推荐:
/employee /employee/21
事实上,这是我的爱好问题,但复数形式更为常见。此外,在资源集合URL上用GET方法,它更直观,特别是GET /employees?state=external
、POST /employees
、PUT /employees/56
。但最重要的是:避免复数和单数名词混合使用,这显得很是混乱且容易出错。
不推荐作法:
GET /employees GET /externalEmployees GET /internalEmployees GET /internalAndSeniorEmployees
为了让你的URL更小、更简洁。为资源设置一个基本URL,将可选的、复杂的参数用查询字符串表示。
GET /employees?state=internal&maturity=senior
RESTful Web服务应使用合适的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" }
使用小驼峰命名法做为属性标识符。
{ "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 // 推荐
从始至终,都使用版本号发布您的RESTful API。将版本号放在URL中以是必需的。若是您有不兼容和破坏性的更改,版本号将让你能更容易的发布API。发布新API时,只需在增长版本号中的数字。这样的话,客户端能够自如的迁移到新API,不会因调用彻底不一样的新API而陷入困境。
使用直观的 “v” 前缀来表示后面的数字是版本号。
/v1/employees
你不须要使用次级版本号(“v1.2”),由于你不该该频繁的去发布API版本。
一次性返回数据库全部资源不是一个好主意。所以,须要提供分页机制。一般使用数据库中众所周知的参数offset和limit。
/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=23¶2=432
在这种状况下,API响应不会返回任何资源。而是执行一个操做并将结果返回给客户端。所以,您应该在URL中使用动词而不是名词,来清楚的区分资源请求和非资源请求。
提供对特定资源的搜索很容易。只需使用相应的资源集合URL,并将搜索字符串附加到查询参数中便可。
GET /employees?query=Paul
若是要对全部资源提供全局搜索,则须要用其余方法。前文提到,对于非资源请求URL,使用动词而不是名词。所以,您的搜索网址可能以下所示:
GET /search?query=Paul //返回 employees, customers, suppliers 等等.
理想状况下,不会让客户端本身构造使用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" } ] }