RESTful Api 规范


Choerodon 符合 REST 原则,它是一个RESTful 架构。

RESTful API Design 名词定义

  • Resource: 一个简单的实例。有一些属性或者一些子资源,子资源可以是一个简单的资源或者一组资源例如:book, user
  • Collection: 一组同类的资源对象。例如:books, users
  • HTTP: 网络协议

HTTP Verbs

  • GET(SELECT):从服务器取出资源(一项或多项)。
  • POST(CREATE):在服务器新建一个资源。
  • PUT(UPDATE):在服务器更新完整的资源(客户端提供改变后的完整资源)。
  • DELETE(DELETE):从服务器删除资源。

Resource Oriented Design

设计流程

  • 确定一个API提供什么类型的资源
  • 确定资源之间的依赖关系
  • 基于类型和依赖关系确定资源的命名
  • 确定资源的结构
  • 为资源添加最少的方法

Resource Names

资源是一个实体对象,那么资源名就是这个资源的标识。

一个资源名应该由Resource ID,Collection ID 和API Service 名组成。

例1:存储服务有 buckets 的集合,其中每个桶包含一个 objects 集合。

API Service NameCollection IDResource IDSub-Collection IDSub-Resource ID
//gateway.com.cn/storage/buckets/bucket-id/objects/object-id

例2:电子邮件服务用户的集合。sub-resource 每个用户都有一个设置,设置 sub-resource 有许多其他的子资源,包括 customFrom:

API Service NameCollection IDResource IDSub-Collection IDSub-Resource ID
//gateway.com.cn/mails/users/name@example.com/settings/customFrom

Resource ID

  • Resource ID 标识着资源属于父资源中。
  • Resource ID 可能不止一个单词,也有可能是一个相对路径。
  • Resource ID 必须清楚地被记录,无论是客户端,服务器,或第三方。

Collection ID

  • Collection ID 必须是有效的程序标识符。
  • Collection ID 必须是驼峰形式的复数结构,如果没有复数形式,应使用单数。
  • Collection ID 必须是清晰简洁的英文单词。
  • Collection ID 避免使用笼统的表示,例如objects、values、types

Action 命名规范

基本规范

  • 使用”/“表示层级关系
  • url 不能以”/“结尾
  • url 中不能包含空格””
  • url 中不能以文件后缀结尾
  • url 中字母小写,单词间加下划线
  • 不要再url中添加CRUD

类别

DescriptionAction NameHTTP MappingHTTP Request BodyHTTP Response Body
查询所有listGET N/AResource list
获取单个资源queryGET N/AResource
创建单个资源createPOST ResourceResource
更新单个资源updatePUT ResourceResource
删除单个资源deleteDELETE N/AEmpty

List

List 方法接受一个 Collection id 和0或多个参数作为输入,并返回一个列表的资源。

  • List 必须使用 GET 方法
  • 接口必须以 collection id 结尾。
  • 其他的请求字段必须作为 url 查询参数。
  • 没有请求体,接口上必须不能包含request body。
  • 响应体应该包含一组资源和一些可选项元数据。

Query

Query 方法接受一个 Resource name 和0或多个参数,并返回指定的资源。

  • Query 必须使 GET 方法。
  • 请求url 必须包含 Resource name
  • 其他的请求字段必须作为 url 查询参数。
  • 没有请求体,接口上必须不能包含request body。
  • 响应体应该返回整个资源对象。

Create

Create 方法接受一个 Collection id ,一个资源,和0或多个参数。它创建一个新的资源在指定的父资源下,并返回新创建的资源。

  • Create 必须使用 POST 方法。
  • 应该包含父资源名用于指定资源创建的位置。
  • 创建的资源应该对应在request body。
  • 响应体应该返回整个资源对象。
  • Create 必须传递一个resource,这样即使resource 的结构发生变化,也不需要去更新方法或者资源结构,那些弃用的字段则需要标识为“只读”。

Update

Update 方法接受一个资源和0或多个参数。更新指定的资源和其属性,并返回更新的资源。

  • 除了Resource Name 和其父资源之外,这个资源的所有属性应该是可以更新的。资源的重命名和移动则需要自定义方法。
  • 如果只支持一个完整对象的更新,Update 必须使用 PUT 方法。
  • Resource Name必须包含在请求的url中。
  • 资源应该对应在request body。

Delete

Delete 方法接受一个Resource Name 和0或多个参数,并删除指定的资源。

  • Delete 必须使用 DELETE 方法。
  • Resource Name 必须包含在请求的url中。
  • 没有请求体,接口上必须不能包含request body。
  • 如果是立即删除,应该返回空
  • 如果是启动一个删除操作,应该返回一个删除操作。
  • 如果只是标识某个资源是“被删除的”,应该返回一个更新后的资源。
  • 如果多个删除请求删除同一资源,那么只有第一个请求才应该成功,其他的返回not found。

自定义方法

自定义的方法应该参考5个基本方法,应该用于基本方法不能实现的功能性方法。可能需要一个任意请求并返回一个任意的响应,也可能是流媒体请求和响应。

可以对应a resource, a collection 甚至 a service。

  • 自定义方法应该使用 POST 方法。不应该使用PATCH 方法。
  • 自定义方法对应的 Resource Name 或者 Collection id 必须包含在请求的url中。
  • 如果使用的HTTP 方法接受request body,则需要对应一个请求体。
  • 如果使用的HTTP 方法不接受request body,则需要声明不使用body,并且参数应该作为url查询参数。

批量添加

DescriptionAction NameHTTP MappingHTTP Request BodyHTTP Response Body
批量添加batchCreatePOST /batch_createResource* listResource IDS

批量删除

DescriptionAction NameHTTP MappingHTTP Request BodyHTTP Response Body
批量删除batchDeletePOST /batch_deleteResource IDSEmpty

更新单个资源中的属性

DescriptionAction NameHTTP MappingHTTP Request BodyHTTP Response Body
更新资源的状态updateAttributePOST /:attribute?value=N/A{“key”:“”,“value”:“”}
更新用户的年龄updateAgePOST /v1/users/1/age?value=20N/A{“key”:“age”,“value”:“20”}

对资源执行某一动作

  • 比如发送消息,启用什么功能。
  • 如果是针对资源,则Action Name为动词。
  • 如果是针对资源的属性,则Action Name为动词+属性名。请求以动词结尾,属性作为参数。
    DescriptionAction NameHTTP MappingHTTP Request BodyHTTP Response Body
    对资源执行某一动作customVerbPOST /custom_verbN/A*
    取消某种操作cancelPOST /cancelN/ABoolean
    从回收站中恢复一个资源undeletePOST /v1/projects/1/undeleteN/ABoolean
    检查项目是否重名checkNamePOST /v1/projects/1/check?name=N/A

查询某一资源的单个属性

  • 对于单个资源的所有的查询Action Name,都需要以query开头。
  • Action Name以query+属性名结尾
    DescriptionAction NameHTTP MappingHTTP Request BodyHTTP Response Body
    查询资源的某属性queryAttributeGET /:attributeN/A{“key”:“”,“value”:“”}
    查询用户的年龄queryAgeGET /v1/users/1/ageN/A{“key”:“age”,“value”:“25”}
    查询用户下的项目queryProjectsGET /v1/users/1/projectsN/A{“key”:“projects”,“value”:[]}

查询collection 的数量

  • 计算集合自身的数量,使用count作为Action Name
  • 计算资源中子集合的数量,使用count+集合名作为Action Name
    DescriptionAction NameHTTP MappingHTTP Request BodyHTTP Response Body
    查询Collection 的数量countGET /countN/A{“key”:“”,“count”:“”}
    查询组织的数目countGET /v1/organizations/countN/A{“key”:“organizations”,“count”:“100”}
    查询用户下的所有项目数量countProjectsGET /v1/users/1/projects/countN/A{“key”:“projects”,“count”:“100”}

复杂条件查询

  • 对于collection的所有查询Action Name,都需要以list开头。
  • 查询的条件中,如果条件为一到两个,使用ByAnd。eg.: listByUserIdAndName
  • 如果查询条件大于3个,则使用ByOptions,查询条件作为请求体传入。eg: listByOptions

版本控制

  • 主版本号必须作为包名的最后一个字符。如:com.choerodon.controller.v1
    版本兼容的修改:

  • 添加一个服务接口

  • 添加一个API方法
  • 添加一个请求字段
  • 添加一个相应字段
  • 添加一个字段的枚举值
    版本不兼容的修改:

  • 删除或重命名一个服务,接口,方法,枚举值

  • 改变一种HTTP method
  • 改变字段的类型
  • 改变一个resource name

Demo

  1. @RestController
  2. @RequestMapping("/v1/users")
  3. public class UserController {
  4. @GetMapping("/")
  5. public ResponseEntity<User> list() {
  6. return null;
  7. }
  8. @GetMapping("/{id}")
  9. public User query(@PathVariable("id") String id) {
  10. return null;
  11. }
  12. @PostMapping("/")
  13. public ResponseEntity<User> create(@RequestBody User user) {
  14. return null;
  15. }
  16. @PutMapping("/{id}")
  17. public ResponseEntity<User> update(@PathVariable("id") String id, @RequestBody User user) {
  18. return null;
  19. }
  20. @DeleteMapping("/{id}")
  21. public ResponseEntity<User> delete(@PathVariable("id") String id) {
  22. return null;
  23. }
  24. @PostMapping("/batch_create")
  25. public ResponseEntity<User> batchCreate(@RequestBody List<User> users) {
  26. return null;
  27. }
  28. @PostMapping("/batch_delete")
  29. public ResponseEntity<User> batchDelete(@RequestBody List<User> users) {
  30. return null;
  31. }
  32. @PostMapping("/age")
  33. public ResponseEntity<User> updateAge(@RequestParam("value") Integer age) {
  34. return null;
  35. }
  36. @PostMapping("/{:id}/undelete")
  37. public ResponseEntity<User> undelete(@PathVariable("id") String id) {
  38. return null;
  39. }
  40. @PostMapping("/check")
  41. public ResponseEntity<User> checkName(@RequestParam("name") String name) {
  42. return null;
  43. }
  44. @GetMapping("/{:id}/age")
  45. public ResponseEntity<User> queryAge(@PathVariable("id") String id) {
  46. return null;
  47. }
  48. @GetMapping("/{:id}/name")
  49. public ResponseEntity<User> queryByUserIdAndName(@PathVariable("id") String id, @RequestParam("name") String name) {
  50. return null;
  51. }
  52. @GetMapping("/{:id}/projects/count")
  53. public ResponseEntity<User> countProjects(@PathVariable("id") String id, @RequestParam("name") String name) {
  54. return null;
  55. }
  56. @GetMapping("/")
  57. public ResponseEntity<User> listByOptions(@RequestBody Map<String, Object> options) {
  58. return null;
  59. }
  60. }