REST API


理论基础

关于 REST API 学院君之前专门写过一篇教程:RESTful 风格 API 设计原则与最佳实践,感兴趣的同学可以先看下这篇教程来了解 REST API 的一些理论细节,这里不做过多阐述了,简单来说,REST 或者叫 RESTful 表示资源状态的转移(Representational State Transfer),它只是一种构建资源 API 的方法和最佳实践,在很大程度上依赖于 HTTP 方法和状态代码,并不是一个严格的协议(如SOAP)。

在 Laravel 项目中,我们可以通过框架提供的 Route::resource 方法轻松创建 RESTful 风格的资源 API:

Route::resource('posts', PostController::class);

我们可以在命令行通过 route:list Artisan 命令查看对应的 API 路由:

image-20221207131958324

这里面能看到 HTTP 请求方法,路由以及对应的控制器方法,但是没有体现出 HTTP 状态码,我们通过下面这个表格进行补充:

Method URI 描述 状态码
GET /api/v1/posts 获取所有文章 200 - OK
GET /api/v1/posts/{post} 获取单篇文章 200 - OK,404 - Not Found
POST /api/v1/posts 创建新文章 201 - Created,429 - Unprocessable Entity(请求太频繁)
PUT/PATCH /api/v1/posts/{post} 更新某篇文章 204 - No Content,404 - Not Found,429 - Unprocessable Entity
DELETE /api/v1/posts/{post} 删除某篇文章 204 - No Content, 404 - Not Found

当然,对于增删改这些需要对请求进行认证的接口,如果在未认证情况下,会返回 401 状态码:

  • 401 - Unauthorized 表示用户需要认证

而对于删除和更新操作而言,不仅要认证,还要做权限校验,如果没有权限,通常返回 403 状态码:

  • 403 - Forbidden,表示无权访问该接口

PUT vs. PATCH

这些 HTTP 请求方法里,除了 PUTPATCH 之外,其他都比较好理解,在使用的时候也很容易判断,两者比较相似,但也还是有区别的,它们的主要区别是:

  • PATCH 方法用于资源部分更新的场景,因此在请求数据中,指定的是部分属性数据;
  • PUT 方法用于资源整体替换的场景,因此在请求数据中,需要指定所有属性的数据。

以文章发布为例,首次发布后(文章创建与发布是分开的两个操作),会更新发布时间字段,这个时候,应该使用 PATCH 请求方法:

PATCH /api/v1/posts/{post}/publish
{
 "publish_at": "2022-12-07"
}

而文章发布后,后续通过编辑表单更新提交,则需要使用 PUT 请求方法:

PUT /api/v1/posts/{post}
{
 "title": "Test post",
 "slug": "test-post",
 "body": "<div>This is a test post.<div>"
}

除此之外,PUTPATCH 还有一些其他的不同点需要关注:

  • 一个成功的 PUT 请求响应 Body 应该是空的,即 204 响应。因为客户端已经把所有属性数据提交给更新接口了,所以不需要返回任何实体内容。
  • 而一个成功的 PATCH 请求则应该包含响应 Body,因为服务端可能会基于客户端提交的属性数据做一些计算和调整。
  • PUT 请求应该是幂等的, 这意味我们可以编写批处理脚本发起对同一个接口同一行数据的多次 PUT 请求,而不会有任何副作用。
  • PATCH 请求则不一定是幂等的。

嵌套资源

REST API 给我们的另一个启发是我们可以在 API 路径中使用嵌套资源来代替查询字符串。

还是以文章系统为例,要获取某篇文章下的所有评论,按照正常逻辑可以这么做:

GET /api/v1/comments?post_id={id}

而使用 REST 风格的 API 设计,我们可以这么做:

GET /api/v1/posts/{post}/comments

这样,无论从 API 接口的语义性、可读性和可维护性来说,后者都要优于前者:

  • 语义性:评论依附于文章才有意义,脱离了具体的文章,评论就会变成不知所云;
  • 可读性:第二种方式可以很直观的看出来要获取指定文章的所有评论,对于第一种方式,在接口定义的时候没有查询字符串的情况下,我们很难预判这个接口是干嘛的;
  • 可维护性:查询字符串的方式就是个大杂烩,你永远不知道查询字符串会包含哪些条件,相应的,后续维护成本也很高。

点赞 取消点赞 收藏 取消收藏

<< 上一篇: TDD 基本流程

>> 下一篇: 从 API Resource 开始