REST API设计实践

1 RESTful

1.1 什么是RESTful

官方解释

Representational State Transfer 的简称,即 表现层状态转移。

人看的解释

  • REST指一组架构约束条件和原则, 如果一个架构符合 REST 的约束条件和原则,就称之为 RESTful 架构
  • RESTful 是一种软件架构风格,而不是标准

大神总结

  • URL定位资源
  • HTTP动词(GET, POST, DELETE, PUT)描述操作
  • HTTP状态码表示响应结果

总的来说,RESTful是一种web服务设计风格,风格意思就是大家默认的但不是强制的。

1.2 REST规范

用URL定位资源

REST 的主体是资源,所谓“资源”,就是网络上的一个具体信息,例如:一张图片,一段文字、一种服务。总之就是一个实际存在的东西,而 URL 就是用来指向这个资源的。

例如:

https://api.example.com/users

用HTTP动词描述操作

对资源的操作,无外乎 CRUD(增删改查),RESTful 中,每个 HTTP 动词对应一个 CRUD 操作。

  • GET:对应 Retrieve 操作。GET请求从不改变资源的状态。无副作用。GET方法是幂等的。GET方法具有只读的含义。因此,你可以完美的使用缓存。
  • POST:对应 Create 操作
  • DELETE:对应 Delete 操作
  • PUT:对应 Update 操作

用HTTP状态码表示响应结果

RESTful Web服务应使用合适的HTTP状态码来响应客户端请求

  • 2xx - 成功 - 一切都很好
  • 4xx - 客户端错误 - 如果客户端发生错误(例如客户端发送无效请求或未被授权)
  • 5xx – 服务器错误 - 如果服务器发生错误(例如,尝试处理请求时出错)

2 RESTful API设计实践

设计RESTful API可能很棘手,因为没有官方和强制标准。基本上,实现API的方法有很多种,但其中一些方法已在实践中得到证明并被广泛采用。这篇文章涵盖了构建 HTTP 和 RESTful API 的最佳实践。我们将讨论 URL 结构、HTTP 方法、创建和更新资源、设计关系、有效负载格式、分页、版本控制等等。

2.1 URL设计建议

用名词代替动词表示资源

这让你的API更简洁,URL数目更少

正确示例:
GET /employees
GET /employees?state=external
POST /employees
PUT /employees/56

错误示例:
/getAllEmployees
/getAllExternalEmployees
/createEmployee
/updateEmployee

资源用复数表示,全部小写,用_或-连接,不要使用驼峰命名法

这是个人爱好问题,但复数形式更为常见。最重要的是:避免复数和单数名词混合使用,这显得非常混乱且容易出错。

推荐:

/employees
/employees/21

不推荐:

/employee
/employee/21

每个资源使用两个URL

资源集合用一个URL,具体某个资源用一个URL

/employees         #资源集合的URL
/employees/56      #具体某个资源的URL

在URL中强制加入版本号

从始至终,都使用版本号发布您的RESTful API。将版本号放在URL中以是必需的。如果您有不兼容和破坏性的更改,版本号将让你能更容易的发布API。发布新API时,只需在增加版本号中的数字。这样的话,客户端可以自如的迁移到新API,不会因调用完全不同的新API而陷入困境。使用直观的 “v” 前缀来表示后面的数字是版本号。

你不需要使用次级版本号(“v1.2”),因为你不应该频繁的去发布API版本。

正确示例:
/v1/employees

错误示例:
/v1.2/employees

提供分页信息

一次性返回数据库所有资源不是一个好主意。因此,需要提供分页机制。通常使用数据库中众所周知的参数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": [
    //...
  ]
}

对可选的、复杂的参数,使用查询字符串(?)

为了让你的URL更小、更简洁。为资源设置一个基本URL,将可选的、复杂的参数用查询字符串表示。

正确示例:

GET /employees?state=internal&maturity=senior

错误示例:

GET /employees
GET /externalEmployees
GET /internalEmployees
GET /internalAndSeniorEmployees

非资源请求用动词

有时API调用并不涉及资源(如计算,翻译或转换)。

GET /translate?from=de_DE&to=en_US&text=Hallo

//Trigger an operation that changes the server-side state
POST /restartServer
//no body

POST /banUserFromChannel
{ "user": "123", "channel": "serious-chat-channel" }

在这种情况下,不涉及任何资源,服务器执行一个操作并将结果返回给客户端。因此,您应该在 URL 中使用动词而不是名词来清楚地区分操作(RPC-style APIs)和用于领域建模的资源(REST APIs)。

  • 如果一个API已操作为主,应该设计成RPC风格
  • 如果一个API主要用来对相关数据进行CRUD,应该设计成REST风格

2.2 HTTP动词设计建议

HTTP动词语义

  • Get
    • 幂等
    • 只读。GET永远不会改变服务器端资源的状态,它必须没有副作用。
  • PUT
    • 幂等
    • 可用于创建和更新
    • 常用于更新(完全更新)。例如:PUT /employees/1- 更新员工 1
    • 始终在请求中包含整个有效负载。要么全有要么全无。PUT不用于部分更新
  • POST
    • 非幂等
    • 用于创建。例如:POST /employees创建一个新员工
  • DELETE
    • 幂等
    • 用于删除。例如:DELETE /employees/1
  • PATCH
    • 幂等
    • 用于部分更新。例如:PATCH /employees/1- 使用负载中包含的字段更新员工1, 员工1的其他字段不会被更新

2个URL乘以4个HTTP方法就是一组很好的功能。看看这个表格:

POST(创建) DELETE(删除) PUT(更新) GET(查询)
/employees 创建一个新员工 删除所有员工 批量更新员工信息 列出所有员工
/employees/56 (错误) 删除56号员工 更新56号员工的信息 获取56号员工的信息

2.3 HTTP状态码设计建议

RESTful Web服务应使用合适的HTTP状态码来响应客户端请求,其中的大部分HTTP状态码都不会被用到,只会用其中的一小部分。通常会用到一下几个:

2xx:成功 3xx:重定向 4xx:客户端错误 5xx:服务器错误
200 成功 301 永久重定向 400 错误请求 500 内部服务器错误
201 创建 304 资源未修改 401未授权
403 禁止
404 未找到

2.4 响应内容建议

使用小驼峰命名法

使用小驼峰命名法作为属性标识符。

{ "yearOfBirth": 1982 }

不要使用下划线(year_of_birth)或大驼峰命名法(YearOfBirth)。通常,RESTful Web服务将被JavaScript编写的客户端使用。客户端会将JSON响应转换为JavaScript对象(通过调用var person = JSON.parse(response)),然后调用其属性。因此,最好遵循JavaScript代码通用规范

将实际数据包装在一个data字段中

GET响应,PUT、POST 和 PATCH 请求体都应该将实际数据包装data字段

  • 有剩余空间可以添加元数据(例如用于分页、链接、弃用警告、错误消息)
  • 保持数据一致性
  • JSON:API 标准兼容
GET /employees  # 返回data字段中的对象列表

{
  "data": [
    { "id": 1, "name": "Larry" }
    , { "id": 2, "name": "Peter" }
  ]
}

GET /employees/1 # 返回data字段中的单个对象

{
  "data": { 
    "id": 1, 
    "name": "Larry"
  }
}

返回有用的错误信息

除了合适的状态码之外,还应该在HTTP响应正文中提供有用的错误提示和详细的描述。这是一个例子:

请求

GET /employees?state=super

响应

// 400 Bad Request
{
  "errors": [
    {
      "status": 400,
      "detail": "Invalid state. Valid values are 'internal' or 'external'",
      "code": 352,
      "links": {
        "about": "http://www.domain.com/rest/errorcode/352"
      }
    }
  ]
}

提供用于导航的链接

理想情况下,您不要让您的客户构建 URL 以使用您的 REST API。让我们考虑一个例子。

客户想要访问员工的工资报表。因此,他必须知道他可以通过将查询参数附加salaryStatements到员工 URL(例如/employees/21/salaryStatements)来访问工资报表。这种字符串连接容易出错、脆弱且难以维护。如果您更改访问 REST API 中的薪水报表的方式(例如,现在使用“salary-statements”或“paySlips”),所有客户端都将中断。

{
  "data": [
    {
      "id":1,
      "name":"Paul",
      "links": [
        {
          "salary": "http://www.domain.com/employees/1/salaryStatements"
        }
      ]
    }
  ]
}

如果客户端完全依赖链接来获取工资单,那么即使您更改 API,他也不会中断,因为客户端将始终获得有效的 URL(只要您在 URL 更改的情况下更新链接)。另一个好处是您的 API 变得更具自我描述性,并且客户不必经常查找文档。

适当的设计关系

让我们假设每个employee都有 amanager和几个teamMembers。在API中设计关系基本上有三个常用方式:链接、外键和嵌入。

它们都是有效的,正确的选择取决于用例。主要考虑

  • 客户端的访问模式
  • 可容忍的请求次数
  • 有效负载大小
链接:负载小,但要获取完整数据访问次数过多

{
  "data": [
    { 
      "id": 1, 
      "name": "Larry",
      "relationships": {
        "manager": "http://www.domain.com/employees/1/manager",
        "teamMembers": [ 
          "http://www.domain.com/employees/12",
          "http://www.domain.com/employees/13"
        ]
        //or "teamMembers": "http://www.domain.com/employees/1/teamMembers"
      }
    }
  ]
}

外键:负载大小适中,没有重复,但用户需要解析数据,使用麻烦

{
  "data": [
    { 
      "id": 1, 
      "name": "Larry",
      "relationships": {
        "manager":  5 , 
        "teamMembers": [ 12, 13 ]
      }
    }
  ],
  "included": {
    "manager": {
      "id": 5, 
      "name": "Kevin"
    },
    "teamMembers": [
      { "id": 12, "name": "Albert" }
      , { "id": 13, "name": "Tom" }
    ]
  }
}

嵌入:用户获取数据最方便,但负载过大,引用数据会重复

{
  "data": [
    { 
      "id": 1, 
      "name": "Larry",
      "manager": {
        "id": 5, 
        "name": "Kevin"
      },
      "teamMembers": [
        { "id": 12, "name": "Albert" }
        , { "id": 13, "name": "Tom" }
      ]
    }
  ]
}

3 One More Things

3.1

参考资料


posted @ 2021-07-28 15:06  ideal_1412  阅读(124)  评论(0编辑  收藏  举报