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 和 其他谓词的规则相反,具体如下。
显然操作单个资源实例和操作资源集合并没有进行有效区分。
如果是预先知道资源的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
。
综上,URL的范式是
/VERSION/api/[RESOURCE|RESOURCE-INSTANCE]/[SUB-RESOURCE|SUB-RESOURCE-INSTANCE]
4 HTTP API 和函数名称映射
这在历次实践中并没有涉及到,出于简化的考虑有需求,列出如下。
VERB & URL | 函数包名/名称 | 常用方法名 |
| v1.book.post.one | create |
| v1.book.post.list | create |
| v1.book.post.file | create |
| v1.book.post.ticket | create |
| v1.book.del.one | delete |
| v1.book.del.list | delete |
| v1.book.put.one | update |
| v1.book.put.list | update |
| v1.book.patch.one | update |
| v1.book.patch.list | update |
| v1.book.get.one | get |
| v1.book.get.list | list |
| v1.book.comment.post.one | create |
| v1.book.comment.del.one | delete |
| v1.book.comment.del.list | delete |
| v1.book.comment.put.one | update |
| v1.book.comment.put.list | update |
| v1.book.comment.get.one | get |
| 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 函数命名规则。
参考资料
- Web API design best practices - Azure Architecture Center | Microsoft Learn
- API 设计指南 | Google Cloud
- Rest API - Best Practices for Designing Amazon API Gateway Private APIs and Private Integration
- Representational state transfer - Wikipedia
- Clean URL - Wikipedia