关于 restful api 想必不用多说,已经有很多文章都阐述过它的设计原则,但遵循这个原则可以让你的 API 接口更加规范吗?以下是我对 restful api 风格的一些思考🤔。
思考
此时不妨思考一个问题,现在以下几个接口,你会怎么去设计 url 路径?
- 查询文章
- 查看文章详情
- 创建文章
- 更新文章
- 删除文章
- 查看我的文章
- 查看他人的文章
前 5 个接口想必不难设计,这边就给出标准答案。
- 查询文章
GET /articles
- 查看某篇文章详情
GET /articles/:id
- 创建文章
POST /articles/
- 更新文章
PUT /articles/:id
- 删除文章
DELETE /articles/:id
当然,我相信肯定也有GET /article—list
POST /add-article
这样的答案,不过这些不在 restful api 风格的范畴,就不考虑了。
而这时 查看我的文章 或许就需要稍加思考,或许你会有以下几种方式
GET /my-articles
从资源角度来看肯定不好,因为此时在 url 不能很直观地体现请求资源,同时在控制器文件(controller) 就与 article 分离了,并且还占用 了 / 下路径。GET /articles/mine
则又不那么遵循 restful api 风格,挺违和的。
那么这时不妨遵循 资源从属关系,在这里 文章所属的对象就用户,因此查看他人的文章可以这么设计GET /users/:userId/articles
获取特定用户(userId)的文章列表。
而 查看我的文章 同样也可用此 URL,只需将 userId 更改为自己的便可。从 api 的 URL 来看是很舒服了,但是从代码开发的角度上问题又有了问题了。。。
对于 user 资源,是不是也有查询,创建,更新,删除等接口,即 查询用户 GET /users
,创建用户POST /users/
等等。。
我是不是就需要在 user 这么重要的资源控制器上去添加一些其他方法,所对应的代码就如下所示
@Controller('users')
export class UserController {
constructor(private userService: UserService, private articleService: ArticleService) {}
@Get()
async list(@Query() dto: UserQueryDto) {
return this.userService.findAll(dto)
}
@Get(':id')
async info(@Param('id') id: number) {
return this.userService.findOne(id)
}
@Post()
async create(@Body() dto: UserCreateDto) {
await this.userService.create(dto)
}
// 省略有关 User 部分接口,以下是其他 user 下的资源接口
@Get(':userId/articles')
async articles(@Param('userId') userId: number) {
return this.userService.findAll(userId, articlesId)
}
@Get(':userId/articles/:articlesId')
async articles(@Param('userId') userId: number, @Param('articlesId') articlesId: number) {
return this.articleService.find(userId, articlesId)
}
}
换做是我,肯定不会希望将用户的代码与文章的代码混杂在一起。解决办法也是有的,可以额外创建一个新的 UserController 文件,专门用于获取用户下的资源(这里指 article),这样可以 即与原有针对 user 资源进行解耦,有可以有比较清晰接口分类。
不过针对这种情况我可能的解决办法是下会额外 起一个别名,例如 author,将 /users/:id/articles
转为 /authors/:id/articles
,不过在这里指向的是用户 id,而不是新建一个 author 实体(资源)。
这里的 id 会根据情况而定,假设业务中需要创建 author 实体的情况下,对 author(作者)这一身份有一些操作,如普通用户变成一个作者,获取所有作者 ,那么这么做就再适合不过了。
在比如说一个更鲜明的例子 商店(store) 与 商品(product)。
业务再稍微复杂一下,现在要为业务增加以下几个功能,你又会如何设计
- 收藏他人文章
- 获取我收藏的文章
答案应该会有两种,即 POST /articles/:articleId/collections
与 POST /collections
而这就令我特别头疼,因为这两个都符合 restful api 风格,也确实都能很好的满足业务功能。于是在我尝试抓包拥有相关的网站后,我发现几乎都是后者的 url。后来一想,前者更像是获取某种资源,而不是用于创建资源。后者确实更能胜任多数场景,比如说现在我需要收藏某个专栏,那么我用 POST /collections
足以胜任,只需要传递 条目id与条目类型,后端根据这两个条件找到对应条目数据便可。假设后续业务多一个资源需要收藏也不成问题。但换做前者的话,就得再多写一个重复性接口。
抽象资源
restful 更多是针对实际存储的资源,核心是名词,对于增删改查的业务可以说非常适合,但现实情况下不只有增删改查,就例如上述的收藏功能。
对于一些个别接口需要另外表达,如 登录 POST /login
、获取个人信息 GET /profile
对于一些非增删改查的操作,还是使用 RPC 式的 API 更为实在,即 POST /命名空间/资源类型/动作
,至少不用再为某个操作决定 PATCH/PUT 还是 POST/DELETE。