【API设计与开发实践】第2篇 Restful API 设计最佳实践的四个重要改进

时间:2022-11-18 15:02:24

Restful API 设计最佳实践已经被讨论过多次,其中命名规则有共识也有差异。从函数实现的角度出发,基于简单、明确的原则,在考虑对接 RPC API 的情况下,可以发现一些冲突和理解上的难点,本文做出了四项改进。


1 资源名称单复数问题

资源名称使用单数或者复数本是习惯约定,只要表意明确即可。本文强烈推荐使用单数,理由如下。

(1)使用单数顺应中文常识

虽然指人时有“我”、“我们”、“你”、“你们”,区分单复数。但指物或者类时,比如“一批书”和“一本书”,都是用“书”,不使用“书们”,没有区分单复数。其他示例如,“一篇文章”、“全部文章”,一般不使用“文章们”,“一位工程师”、“多位工程师”,一般不使用“工程师们”。

(2)单复数转换引起麻烦

英文的单复数转换规则复杂,有些单词单复数相同,这增加了理解的难度,降低了准确度。

(3)缩写没有复数

有些资源就是资源分类名称,甚至是缩写,使用复数表示没有意义,反而容易引发歧义。

(4)保持业务术语一致性

使用资源的地方较多,比如资源 URL、结构或者对象名称、数据库表。结构或者对象名称使用单数的习惯非常固定,资源 URL 和数据库表名等使用单数非常简单,可以避免在整体业务中资源名称转换的额外开销和理解成本。

当然对于 mongodb 等文档数据库,数据库表名习惯就是使用复数,那就忽略吧。

(5)简单原则优先

随着零代码平台推出,开发 API 的门槛降低,人员范围变大,单复数转换极有可能就是一个知识痛点。


2 资源分类和 URL 映射

资源的操作方式有很多种,函数的实现是确定的,这就存在了一对多的可能性。

(1)资源的同类操作对应于同一 URL 和 HTTP 操作引发冲突

例如创建图书信息,既可以一次创建一本图书的信息,也可以一次创建一批图书的信息。如果只使用资源名称和 HTTP 谓词,那么结果就是 ​​POST /v1/api/book​​,表示创建一批图书信息的 API,似乎可以解决问题。

但如果对 API 增加约束,比如普通用户使用一次创建一本图书信息的 API,小组管理员使用一次创建一批图书信息的 API,那么就必须开发两个 API 分别配置。显然一个答案并不能满足两个 API 的需求,以往的最佳实践并没有对此类问题给出答案。

(2)函数单一职责要求多个 URL 映射

就此,我们将图书信息创建函数展开,分为四个来讨论如何制定资源 URL 和操作。(1)一次创建一本书的信息,(2)一次创建一批书的信息,(3)使用电子表格文件批量创建一批书的信息,(4)从指定网上的电子书单创建一批书的信息。

对于 HTTP API 来说,当然可以在请求中定义多个参数,既可以又可以,以此处理四件事情。但是对于函数来说,肯定要分类讨论,一次只做一件事情。在精细化管理的情况下,如果一个 HTTP API 对应一个函数最好。

如果可以使用 Header 参数,当然可以。但大多数 http 框架并没有默认支持映射,需要自己处理,从语义理解上落了下风。

(3)关于 POST 的单个资源实例和资源集合操作

网上最佳实践对 POST 的 URL 规则有些奇怪,因为 POST 和 其他谓词的规则相反,具体如下。

【API设计与开发实践】第2篇 Restful API 设计最佳实践的四个重要改进

显然操作单个资源实例和操作资源集合并没有进行有效区分。

如果是预先知道资源的ID,那么可以使用 ​​POST /v1/api/company-1​​​ 或者 ​​​POST /v1/api/company/1​​​,如果不知道呢,是使用 ​​POST /v1/api/company-​​​ 或者 ​​POST /v1/api/company/​​ 吗?显然这些容易造成理解困难。

网上最佳实践并没有给出这个问题的答案,就需要自己实践。

(4)合理对资源分类,结合下级资源明确 URL

将资源来源进行分类,使用上下级资源嵌套,可以明确 URL 如下。

  • 一次创建一本书的信息,使用 ​​POST /v1/api/book​
  • 一次创建一批书的信息,使用 ​​POST /v1/api/book/list​
  • 使用电子表格文件批量创建一批书的信息,使用 ​​POST /v1/api/book/file​
  • 从指定网上的电子书单创建一批书的信息,使用 ​​POST /v1/api/book/ticket​

这里,对于 POST 操作,依照已有习惯,为了兼容其他分类,依旧使用反例。


3 资源ID

读取资源信息时,网上很多实践使用分隔符单独表示资源ID。

例如 ​​GET /v1/api/book/1​​​ 表示读取ID=1的书,​​GET /v1/api/book/1/comment/10​​​ 表示读取 ID=1 的书的 commentID = 10 的评论。

但由于资源分类原则,可能存在 ​​GET /v1/api/book/file​​​ ,表示图书信息的电子表格文件。这里就存在 ​​1​​​ 和 ​​file​​ 的路由冲突,理解上增加了难度,技术处理了增加了难度。

将资源 ID 和资源使用中横线连接表示更加简洁,易于理解。

如将 ​​GET /v1/api/book/1​​​ 表示为 ​​GET /v1/api/book-1​​​,将 ​​GET /v1/api/book/1/comment/10​​​ 表示为 ​​GET /v1/api/book-1/comment-10​​。

【API设计与开发实践】第2篇 Restful API 设计最佳实践的四个重要改进

综上,URL的范式是

/VERSION/api/[RESOURCE|RESOURCE-INSTANCE]/[SUB-RESOURCE|SUB-RESOURCE-INSTANCE]


4 HTTP API 和函数名称映射

这在历次实践中并没有涉及到,出于简化的考虑有需求,列出如下。

VERB & URL 

函数包名/名称

常用方法名

​POST /v1/api/book​

v1.book.post.one

create

​POST /v1/api/book/list​

v1.book.post.list

create

​POST /v1/api/book/file​

v1.book.post.file

create

​POST /v1/api/book/ticket​

v1.book.post.ticket

create

​DELETE /v1/api/book-1​

v1.book.del.one

delete

​DELETE /v1/api/book​

v1.book.del.list

delete

​PUT /v1/api/book-1​

v1.book.put.one

update

​PUT /v1/api/book​

v1.book.put.list

update

​PATCH /v1/api/book-1​

v1.book.patch.one

update

​PATCH /v1/api/book​

v1.book.patch.list

update

​GET /v1/api/book​

v1.book.get.one

get

​GET /v1/api/book-1​

v1.book.get.list

list

​POST /v1/api/book-1/comment​

v1.book.comment.post.one

create

​DELETE /v1/api/book-1/comment-1​

v1.book.comment.del.one

delete

​DELETE /v1/api/book-1/comment​

v1.book.comment.del.list

delete

​PUT /v1/api/book-1/comment-1​

v1.book.comment.put.one

update

​PUT /v1/api/book-1/comment​

v1.book.comment.put.list

update

​GET /v1/api/book-1/comment-1​

v1.book.comment.get.one

get

​GET /v1/api/book-1/comment​

v1.book.comment.get.list

list

表中方法根据需要扩展,主要特点集中在对应函数名了,使用了 http 谓词,使用 one/list 限定词。

这里使用 del 替代 delete,因为一些语言中 delete 是关键字。

one 和 list 是区分对单个/多个对象或者记录的操作。

列出也时考虑 RPC API 的需要,当然 RPC API 的名称还需要考虑 stream 的需要,例如读取流数据时可能使用 v1.book.get.stream,与 v1.book.get.list 进行区分。

这样就实现了 HTTP API 、RPC API 和函数全名的全映射。


5 小结

API 的改进都是本着简洁、明确的原则,从发展的角度出发。本文对同类操作进行了资源分类,避免冲突,尝试规范了 HTTP API 和 RPC API 函数命名规则。


参考资料