什么样的 API 设计能被称为优秀当然是一个非常主观的标准,但是还是有一些客观可考量 API 质量的数据,比如
不管是前端程序员还是后端程序员,都少不了跟 API 打交道。后端需要把 API 设计和实现出来,而前端程序员需要把界面逻辑和 API 接起来,因此对于 REST 的设计规则有一些基本了解,不管你是前端还是后端,都会有很大帮助。
之前在厂里设计了一些还算被广泛使用的 API, 因此我写了这篇文章,结合之前的经验总结了一些要点。希望作为一个参考,可以帮助大家
当然我想要说明的是,设计 API 在一定范围内是有规律可循的,但是太过抠细节则会陷入无穷无尽地“宗教版”争论中,所以请大家理论讨论。
1
KalaSearch OP |
2
abbycin 2020-07-29 07:24:49 +08:00 via Android 5
我八股文写得特别好
|
3
baiyi 2020-07-29 07:30:25 +08:00 2
我认为在设计过程中,需要考虑 HTTP 方法的幂等性。比如 Github 的 Star 操作,为什么是 PUT 而不是 POST,就是从幂等性方面考虑的
|
4
KallyDev 2020-07-29 07:31:05 +08:00 via iPhone 19
|
5
shijianit 2020-07-29 08:21:40 +08:00
如果要接口全部加密,get 方式请求,不是会暴露出来 id 数据吗?
|
6
xuanbg 2020-07-29 08:22:37 +08:00
修改密码和重置密码怎么设计?软删和硬删同时存在怎么办?
|
8
bsg1992 2020-07-29 08:43:48 +08:00 1
这种只适合对外的 api 并且功能单一
一个 ERP 查询几十个字段你用 get? |
9
gnozix 2020-07-29 08:51:17 +08:00
想问问下载接口应该怎么设计
|
11
nockyQ 2020-07-29 09:12:50 +08:00
|
12
lolizeppelin 2020-07-29 09:36:29 +08:00 1
paypal api 和沙箱比微信支付漂亮太多了
但是不妨碍 paypal 垃圾微信支付好用...... |
13
KalaSearch OP @nockyQ 啊是的,stripe 用的是这种。感谢你的新信息
|
14
KalaSearch OP @baiyi 感谢 <3
|
15
KalaSearch OP @KallyDev 这个很赞,我之前也看过,谢谢提出来,我会加到文章里
|
16
KalaSearch OP @shijianit id 应该默认认为已经暴露,藏不住。楼下说的用 uuid 是个好办法,不过不管怎么样不应该认为 id 可以隐藏起来达到安全的目的。(安全我懂得不多,更详细等楼下们讨论啦)
|
17
KalaSearch OP @gnozix 能说说具体场景吗?文件下载?
|
18
wellsc 2020-07-29 09:53:32 +08:00
restfool (逃
|
19
MrTreasure 2020-07-29 10:25:34 +08:00
还是缺乏具体场景,文中的内容就是是属于 restful 的标准。但是对于难点没有很好的讲解,比如 restful 如何返回错误。区分 HTTP 错误以及业务错误
|
20
ZacksT 2020-07-29 10:58:18 +08:00 2
你的 REST API 满足公司 /研发团队标准就是好的设计。接口标准可以帮助开发者规避(公司研发 /团队研发)遇见过的问题或可能遇到的问题,也可以让组内代码标准化,统一化。
就拿一个简单的例子,一个分页查询的接口。其包含分页条件与不定量的查询条件。 你可以通过 GET 方式请求,将参数定义在 url 上。也可以通过 POST 方式请求,将查询参数定义在 requestbody 里。 第一个方式,在遇到查询条件复杂的情况下,会导致 url 过长。 第二种方式,又会产生很多 VO 的定义。 采用混搭又让前后端代码变得混乱。 我个人认为,只要满足团队要求的 API,就是好的 API 。具体实现各有好处,看团队取舍了 |
21
KalaSearch OP |
23
ibreaker 2020-07-29 11:57:46 +08:00 2
小伙子很活跃啊,天天都能刷到你
|
24
lovedebug 2020-07-29 12:40:40 +08:00
@ZacksT 分页 GET 查询一般只带 limit 和 page,少量支持投影和过滤,如果有带其他复杂的 query 条件,其实更应该走 POST search 自定义方法
|
25
ieiayaobb 2020-07-29 13:09:32 +08:00
Get by Id 的比较明确,如果是 Get by name 这种,name 是唯一的怎么设计比较好?不想用 query 是因为不想在 name 不存在的时候返回空数组,而是希望也能和 Get by id 一样返回 404
|
26
xjchenhao 2020-07-29 16:27:06 +08:00
修改密码和重置密码,逼死强迫症😂
|
28
grzhan 2020-07-29 17:27:59 +08:00 5
之前负责撰写公司的 API 规范,当时也参考了很多包括 Azure ( https://docs.microsoft.com/zh-cn/azure/architecture/best-practices/api-design )、Google Cloud ( https://cloud.google.com/apis/design/ )等公司的规范,大厂的标准往往更加规范,给人很多对于 API 设计上概念理解的启发。
其中感觉最详细的大概是 Zalendo 的: https://opensource.zalando.com/restful-api-guidelines/ ,其中有非常多的实践是可以参考的,也像 RFC 一样规范了 MUST 、SHOULD 、MAY 的遵守分级。 关于文中提到的 REST 表示一个动作,我们参考的更多是 ElasticSearch API 的做法,即将动词加上下划线前缀,作为 POST 方法进行服务,形如: http://cloud.sy/machine/xxxx/_restart 关于这一块 Google API 是用冒号作为前缀的,但一些路由框架会占用冒号作为关键字,因此考虑使用下划线代替。 |
29
gnozix 2020-07-29 18:01:56 +08:00
@KalaSearch 对前台展示的表格数据,以 excel 的格式进行下载;所以需要下载的比较多。感觉 REST 风格不太容易表示需要下载的资源
|
30
wshcdr 2020-07-29 19:10:54 +08:00
值得看一下
|
31
Heanes 2020-07-29 19:16:17 +08:00
同意 8 楼,系统内部交互可能还是“常规”的设计形式
|
32
lovedebug 2020-07-30 00:06:28 +08:00 1
@grzhan 同负责撰写 API 规范,其实关于 list 操作的 filter 功能,在实际 API 设计中有些疑惑使用场景,因为大部分情况下使用一般的 query parameter 就可以解决。我的理解是一般的 query parameter 默认是 and 操作,缺乏 or 操作以及 range value 等功能,而 $filter 主要在 url 中描述若干参数复杂的逻辑运算,如果这么做用 POST 自定义动作不是更好吗?想听一下你的理解。
|
34
szthanatos 2020-07-30 09:01:18 +08:00 via Android
批量操作的实践为什么很少有人谈←_←
|
35
solee 2020-07-30 09:10:45 +08:00
经过几年的实践,我们最后全部统一了用 POST,之前看过一篇亚马逊写的关于 Restful API 设计的改进,加入动词的描述,感觉更合理
|
36
lovedebug 2020-07-30 09:42:19 +08:00
@szthanatos 微软规范有谈的
|
37
lovedebug 2020-07-30 09:43:30 +08:00
@xuanbg 哈哈 跟业务场景有关,如果不想用万能 POST,可能只能在 url query 中支持一些 or 查询,客户在使用我们的 public api 时提出的
|
39
Nolink 2020-07-30 09:58:50 +08:00
收藏了,谢谢分享
|
40
ericls 2020-07-30 10:11:56 +08:00 via iPhone
用现成的 query language 不好吗? 非要把 http headers 滥用成 query 还要自己定义 实现 维护……
|
41
Amit 2020-07-30 10:52:53 +08:00 2
@xuanbg
密码一般都是要做 hash 的,且不能暴露给前端,所以需要对这个字段单独修改,而不能放到完整信息中修改并返回,修改密码是在登录状态下,所以我会设计为 PUT /v1/users/{id}/password (管理员修改用户密码)或 PUT /v1/users/self/password (修改自己的密码),重置密码我理解为非登录状态下修改密码(不确定用户身份),所以我会设计为 PUT /v1/users/password,然后再 body 中提供用户名、验证码等信息。 软删除也是删除,对应用来说如果删除了就是不存在的,应用中不应该能看到,软删除和物理删除同时存在是不合理的,这种情况应该设计一个状态字段区分,而不是使用逻辑删除。 |
42
xuanbg 2020-07-30 12:01:16 +08:00 1
@Amit
修改密码和重置密码我也是一样的处理。在复数形式的资源后面,有时候不但要加动词,还得加属性,以定位到更细一层的资源才行。 我说的软删其实是禁用,只是为了理解方便。业务前端看不到了,也就没得用了。但元数据管理后端应该能看到,毕竟禁用后说不得还会启用。。。硬删当然就是数据灰灰,再也无法恢复的。如果软删用 PUT:/v1/users,那就和修改姓名冲突了。我是这样规划的,修改普通属性 PUT:/v1/users,禁用 PUT:/v1/users/status,删除 DELETE:PUT:/v1/users 。 |
43
jorneyr 2020-07-30 16:09:28 +08:00
RESTful 在 URL 里是禁止使用动词的,但是很多时候有的 URL 中用动词来表达很自然,强制使用 RESTful 的风格的话会很难受
|
44
imhxc 2020-07-30 18:19:45 +08:00
我一直有个问题,请教下。
在实际业务中,各种需求都有,很难严格遵守 RESTful API,拿文章中的示例来说: GET /owners/1/pets/ 获取 id 为 1 的主人的所有宠物 1. 如果区分角色怎么办,比如管理员获取 id 为 1 的主人的所有宠物,结果中包含所有状态的宠物; 2. 其他人需要查看 id 为 1 的主人所有宠物,结果中只返回状态为「可公开」的宠物; 这种怎么设计? |
45
codingbody 2020-07-30 18:22:52 +08:00
我有个问题问大家,为什么安全扫描的时候,不准我使用除了 GET 、POST 之外的请求,我认为请求的方式和安全没啥关系吧
|
46
DeWhite 2020-07-30 18:59:43 +08:00
那个就一句话,吃屎啦。就是没有主语的,国内的很明显主语省略的句子还有很多。
|
47
xcstream 2020-07-30 20:21:36 +08:00
这标题隐含意思就是不 rest 就不优秀(狗头)
|
48
KalaSearch OP @imhxc 用 ACL 来控制,REST endpoint 没办法控制的
|
49
KalaSearch OP @DeWhite 你说的是祈使句,祈使句当然可以没有主语(省略了第二人称主语)
|
50
forgaoqiang 2020-07-31 12:16:24 +08:00
看了下 Discuz Q,真的几斤,完全的 RESTFUL 风格,patch delete 各种方法都用
|
51
grzhan 2020-07-31 13:46:45 +08:00 1
@lovedebug 我个人觉得关于复杂查询不管是用 $filter 还是直接 POST 自定义方法(如 "_search" )都是可以的,具体看自己场景。
事实上我们项目实际实践中,这种情况还是自定义 POST 方法用的比较多 |
52
GavinZZ 2020-07-31 13:52:12 +08:00
??
|
53
GavinZZ 2020-07-31 13:52:38 +08:00
还有个叫车满满的。。。工资给开的还算可以 13K+ 14 薪,但是不推荐去,企业文化很奇葩
|
56
lovedebug 2020-07-31 14:20:31 +08:00 1
@grzhan 主要是 GraphQL 对已有产品的 RESTful API 破坏性过大,ROI 也不够高,另外也考虑在微服务和 k8s 中 GraphQL 中心化并不是一个很完美的方案。其实主要的阻力是项目进度和同事。哈哈哈哈
|
57
dongxiaoxian 2020-07-31 15:09:26 +08:00
好复杂
|
58
ChanKc 2020-07-31 18:57:19 +08:00
@codingbody 没有,但是历史上发生过一些 HTTP server 对 PUT,DELETE 等请求实现不当,导致远程代码执行等漏洞。一些公司就会觉得索性禁了这些请求更好
|
60
wangxiaoaer 2020-08-03 09:23:35 +08:00
这个帖子很有启发啊,顺便问一下,针对楼上一些老哥们提到的复杂的组合条件查询,如果是基于 spring boot + jpa 的应用,如何优雅的实现呢?
|
61
cbasil 2020-08-03 09:31:29 +08:00
设计 API 的目的是为了前端好评? api 接口安全和效率都不需要考虑了吗?你去看看阿里,腾讯等大公司的接口文档,有几个是完全按照 REST API 来设计的。
|
62
lovedebug 2020-08-03 09:53:26 +08:00 1
@cbasil 一是对内为了公司内部统一,减少沟通成本。而是针对 public api 与主流统一,减少用户的集成成本。
|
63
nig001 2020-08-03 14:43:53 +08:00
不错的
|
64
fy 2020-08-04 01:38:35 +08:00 1
@lovedebug #32
这个我做了,默认 and 操作,请求类似这样: /api/topic/list/1?time.ge=1577808000&order=time.desc&select=id,title 前端反馈一般,说是不好理解。语言是 python https://github.com/fy0/slim 问题主要是几处: 1. http header 有限,有的查询条件放不下,其实同时支持提交 body 查询更好些( get 提交 body 是规范允许的,只是很多 http server 选择不解析) 2. 对查询的掌控力度不够。前端提交上来一个请求,说某种情况下希望将某个条件变成 or 查询,这时候做不到。当然这和 orm 还有底层实现有关,这是一个整体设计上的问题。 3. 连表查询比较复杂。 4. 全栈开发会觉得好用,有的纯前端就觉得这是后端偷懒。 所以可能不光是规范问题,还是框架问题,甚至要连同 orm 、表单验证、权限之类做通盘考虑。 @imhxc #44 角色权限 + ACL |
65
sunzhenyucn 2020-08-04 04:42:12 +08:00
请让我默默地 mark 一下
|
66
lovedebug 2020-08-04 09:35:07 +08:00
@fy 感谢回复,是的,get 带参数会有这些问题。
一般对于 simple collection items 的 list(GET 方法)操作,我建议用 order,filter, 这样语义清晰,主要实现集合过滤功能。可以尝试在 filter= X OR Y 这样的形式实现 or 操作 我的理解是对于复杂集合(如 logs 等)或通用操作的模糊搜索还是用 POST + custom method,例如 /v1/items/search,除非可以细化复杂集合为若干简单的集合。 主要这个度不好把握。 当然,从实现简单程度来看,所有的 order,filter,projection 都可以定义为用 post 实现。 |
67
thtznet 2020-08-04 11:15:03 +08:00
看到 API 和表对应,我就知道不用看下去了,太水了。
|
68
jy28520 2020-08-04 11:22:55 +08:00
@KalaSearch 想问下我们现在的业务需要验证用户提交的 SKU 和优惠券是否匹配 请问 URL 应该怎么设计那?
我们会有几条 SKU 和几条优惠券的信息 |
69
b0644170fc 2020-08-04 11:36:51 +08:00
根本不需要 rest, get / post 走天下
|
70
imhxc 2020-08-04 14:24:18 +08:00
@fy 嗯嗯,ACL 是可以解决刚才提的问题。
但是总感觉 REST API 规范有局限性,自己曾经做过 ERP,会经常出现较为复杂的接口,感觉很难严格遵守 REST API 风格。 比如有一些无法区分上下级关系、获取同一个数据,有的需要用 iD 查,有的需要用 MD5 查,总之,实际业务中各种千奇百怪的需求。 我以前自己写接口用 REST API 写着写着就要精神分裂了。。。😓 也可能是我没理解 REST API 的精髓😅 |
71
no1xsyzy 2020-08-05 11:00:25 +08:00 2
@imhxc #70 除非你能直接塞图灵完备的代码进数据库,不然什么都有局限性
就是 SQL 有时不得不分成两个查询( SELECT ),虽然完全就是数据库里的内容,之后可优化为一次数据库交互包含两个查询(避免传输),但一个(对人脑来说)本来看上去非常简单的东西,不通过逻辑检验竟然无法简化。 实际上 RESTful 不是有局限性,而是它就是局限性本身:通过强加某种限制,将(一次) API 请求类比为对(一项)资源的操作,形成某种直觉映射,来理清思路。要 “改” 到 RESTful,并不是改动 API 就行的,而是整个建模得修改。 有人[谁?](忘了谁)认为其实是启发自 Unix 的文件操作。(所以 WebDAV 是 RESTful 最恰当的应用场景) |
73
lolizeppelin 2020-08-05 13:13:45 +08:00
这个论坛早就有人说过了
RESTful 是对 sql 的劣质模仿,没法表达的情况多去了 |
74
no1xsyzy 2020-08-05 14:54:39 +08:00
@lolizeppelin #73 谁?在哪儿说的?
RESTful sql 劣质模仿 site:v2ex.com 只搜出来你说的话…… 从来从来,RESTful 就是个和 SQL 完全相悖的路线 SQL 一直在做得越来越图灵完备,添加各种诡异的、我承认确实像是有那么回事儿的、但其实没有也没关系的功能进去。 RESTful 一直都是那么平铺直叙。谓宾仍然是谓宾,最多用点 HTTP 语义。 “C 是个对 Lisp 的劣质模仿” |
75
lolizeppelin 2020-08-05 15:12:12 +08:00
@no1xsyzy
est 说的 嘿嘿 |
76
no1xsyzy 2020-08-05 15:16:09 +08:00
|
77
lolizeppelin 2020-08-05 15:17:08 +08:00
@no1xsyzy
当然个别字有出入呗,你找他 233333 |
78
est 2020-08-05 16:12:31 +08:00 1
@lolizeppelin
@no1xsyzy 我也不记得在哪里说的了,但是中心思想是,RESTful 本来是对文件读写的一个 增删改查 的封装,最适合拿来做 WebDAV 之类的工具。然而其他的业务的「动作」很可能无法用这 4 个指令覆盖。就多出来了很多奇葩的指令比如 OPTIONS TRACE PATCH 。。。与其这样,还不如直接根据具体业务在 url 里指定动作名称。比如 POST /api/user/login POST /api/order/cancel 然后我是明确反对把 URL 里直接嵌入 resource id 作为路径一部分的。比如 GET /myitem/12345/ 这种,RESTful 一时爽,nginx 日志分析火葬场。 |
79
no1xsyzy 2020-08-05 18:38:56 +08:00
@est #78 本来指令就随便添加,过分绑定到固定四个指令有点先辈的罪或者思维定势。
我觉得 POST .../login 没什么问题,我的某个工具里面 Login 是类名,将 Login 视为名词形式。 同时我觉得 POST .../order/cancellation 也没什么问题,是订单状态改变。DELETE order 和它是根本上不同的两种行为。如同 rm 一样,DELETE 谓词的使用应当慎之又慎。 一般这类框架会有自己的日志的,不用 nginx 分析日志。而且如果不分 /api/* 的 URL 出来的话,也就是 /static/* 让 nginx 处理,其他都归框架管了。而且看到某 PHP 应用的官方部署教程是关掉 /static/* 的日志的…… 基本上 nginx 日志存在有意义的信息就已经是系统层面的大问题了(比如 uwsgi 挂了) |
80
putaozhenhaochi 2020-08-11 20:28:03 +08:00
老哥这么拼
|
81
iplayio2019 2020-08-12 00:54:23 +08:00
@est /user/login 这种可以抽象成 session 资源,restful 很强调“资源”概念,POST /api/sessions,登录就是创建 session 。
注销登录 DELETE /api/sessions/me 取消订单本身就是状态更新,PATCH /api/orders/{orderID} <status>:<取消状态的值> |
82
est 2020-08-12 10:08:43 +08:00
|
83
lovedebug 2020-08-12 10:15:28 +08:00
@est
RESTful 规范描述的是资源,对于非资源的情形一般需要自定义 action,这一方面大厂已经做了详细的设计,落实到具体设计就根据各自情况做了 比如你的描述提到的 1, 一般写成 POST /users/${userId}/login?type=sso 或者 login?user=xxx & type=xxx 2,一般会写成 POST /orders/${orderId}/$spilit 或者 POST /orders/${orderId}/$merge {ids:[]} 3 一般写成 POST /items/$batchUpdate {ids:[]} |
84
est 2020-08-12 10:34:00 +08:00
@lovedebug 其实你 2 和 3 已经是另外一种风格的 URL 设计了。。。还不如干脆一条路走到黑全按照这种风格来设计
1. POST /user/login 2. POST /order/split POST /order/merge 3. POST /item/batchUpdate 多干净统一。 RESTful 就是被 UNIX 那种「所有东西都是文件」思想毒害的。遇到完全不像文件或者资源的东西,瞎搞。 |
85
lovedebug 2020-08-12 10:46:29 +08:00 1
@est 对于自定义 action,RESTful 本来就没有统一,自定义 API 风格各个团队根据自己需要定义就可以
两种方案 1. 将资源 uuid 描述在 URL 中 2. 将资源 uuid 描述在 body 中 我们两人上面的就是这两种方案的体验,没有好和坏,只看对于 API 使用者的可读性。 微软和谷歌,github 对于自定义 action 也是分别有自己的实现 |
86
no1xsyzy 2020-08-12 13:34:32 +08:00 1
@est #84 问题不在于 “一切皆文件”,而在于纯远端操作。
文件是对于 “可读可写” 的抽象。举上述你提到的例子: 1. 登录是一个状态,SSO 是一个多服务端共享的状态,可读可写,而且读写经过客户端传递,与文件这一抽象完美契合不成问题。 sub_site 302 到 //sso_host/user/login?to=sub_site/user/login 然后由 sso_host 确定后 302 传递 token //sub_site/user/login?with_token=~~token~~ 类比: $ cat sso/token | authorize sub_site 2. 拆分合并的核心在于它是个纯远端操作。一般来说在 Unix 下拆分文件,不出意外是 head|tail 或者 awk/sed 之类,或者对特定的文件类型也是专门的提取器而不是单独的拆分装置。然而无法保证拆分的准确性。那么显然,正确的操作应当是写一个专门的脚本完成这件事 —— 类比过来,就是新谓词。 SPLITORDER /orders/<orderId>,请求体发送拆分准则之类的,返回 200 内容是拆分结果。 类比: $ split_order "site/orders/${orderId}" [--options ...] site/orders/a site/orders/b site/orders/c 3. 批量操作可以借用 glob,也可以是单独谓词。后者不必说,前者比如: PATCH /orders/{a,b,c} Content-Type: application/json {"coupon": "foobar"} |
87
est 2020-08-12 14:58:13 +08:00
> SPLITORDER /orders/<orderId>,请求体发送拆分准则之类的,返回 200 内容是拆分结果。
对对对。。就是喜欢 RESTful 原教旨主义者这种一本正经发明 1000 个新词的想法。。。。 反正我对 http 的 verb 就认同 2 个,读是 GET, 写就是 POST 。你们觉得 1000 个新词最血统纯正我也没办法。。。。 |
89
ChristopherWu 2020-08-12 17:25:26 +08:00
|
90
brickxu 2020-08-12 18:44:05 +08:00
你这涉猎够广的,另外一个贴子里还在分析 NoSQL,这边直接换到 API 设计了,然后全是 kala 搜索的域名。
|
91
no1xsyzy 2020-08-12 19:27:24 +08:00 2
@est #87 奇妙,我根本不是原教旨主义者
我跟你讲,原教旨主义者的看法是,拆分订单就是删除旧订单,然后建两个新的。 至于批处理,原教旨主义者认为就应该发 100 个请求,服务器被撑爆就应该扩容、增技术 balabala 。 建新谓词是传播主义者的一支,把 RESTful 当 RPC 用。 |
92
est 2020-08-13 10:29:43 +08:00
> 我跟你讲,原教旨主义者的看法是,拆分订单就是删除旧订单,然后建两个新的。
> 至于批处理,原教旨主义者认为就应该发 100 个请求,服务器被撑爆就应该扩容、增技术 balabala 。 > 建新谓词是传播主义者的一支,把 RESTful 当 RPC 用。 我石化了。。。。 |
93
AlbertChen 2020-09-18 09:38:45 +08:00
@gnozix 返回 JSON, 里边包含下载链接啊
|
94
goodboy95 2020-09-18 10:05:46 +08:00
反正我从来不设计 RESTapi,我只设计 api,只是长的有那么点像 REST
|
95
fhsan 2020-09-18 10:40:05 +08:00
怎么来说呢,内部自己的小项目都是 rest,对外的一律 post,因为你不知道对方有啥骚操作
|
96
kokodayo 2020-09-18 11:55:52 +08:00
@lolizeppelin REST 是,RESTful 就还好啦,毕竟部分思想还是好用的,而且 ful 到什么程度也是开发者自己说了算→_→
|
97
icew4y 2020-09-18 16:44:53 +08:00
restful 就是一残废
|
98
abersheeran 2021-03-09 12:22:47 +08:00
@fhsan 哈哈哈哈哈哈哈哈哈,对。我写的一个 RPC 框架,全都是 POST,具体内容全部走 Body 。大家都很满意(指服务端开发和客户端开发)。
|