什么是好的 Web Api 设计 - 查询与别名的设计
ccmjga 2024-10-21 09:03:01 阅读 52
本系列文章的设计思想,都融入了 让 Java 再次伟大 这个全新设计的脚手架产品中,欢迎大家使用。
查询的设计
依据上一章节的内容,REST LEVEL2 的查询只需要使用 GET 方法与 URI 定位到相应资源就可以了。但实际业务中查询往往涉及更复杂的细节处理,查询参数就是最富有代表性的细节处理之一。
查询参数
在 http method 为 GET 的查询 api 中,使用 urlencode 的传参方式,既在 URI 的末尾 ?后面添加的相应参数。
示例 |
---|
api.linkedin.com/v1/people-search?first-name=Clair |
如上所述,first-name 既为你的查询参数对应的 key 值。通常我们会根据不同的业务信息,使用对应的 key 名称。
不过,偶尔你也会看到下面这样的查询参数。
示例 |
---|
api.instagram.com/v1/users/search?q=jack |
api.instagram.com/v1/users/search?name=jack |
q 是 query 的缩写。为什么这里不使用可以反映实际业务信息的 key 名称?因为 q 往往表示范围搜索。上述第一个示例表示搜索包含 jack 词组的所有用户列表。而第二个示例表示名称为 jack 的所有用户列表。
所以,后端开发人员需要在 api 的设计中,根据实际情况慎重选择是否要使用 q 或者 query 这个单词作为你的查询参数的 key。
最后看看微软是如何设计搜索引擎的查询参数的。
示例 |
---|
https://www.bing.com/search?q=URI+test |
当 uri 中存在无法直接使用的字符时,会用一种百分号编码的方式对字符进行处理。例如%E3%81 等。你可能会认为使用 ASCII 字符集就可以高枕无忧了。但是应该各位注意,%、&、+等字符也需要百分号编码。而空格符号在百分号编码中会被编码为+号。
分页查询
相对位置分页
分页查询的要点在于如何设计分页参数。通常来说基于和数据库类似的 limit 与 offset 的组合相对更通俗易懂。
limit=50&offset=100
但是由于查询往往会涉及到数据库的读操作,而在数据量很大的情况下基于 limit 与 offset 的相对位置的查询组合的缺陷会逐渐暴露出来。试想下面这样的查询参数。
limit=50000&offset=10000
这十分可怕,类似于这样的查询请求对服务器和客户端都是灾难。
绝对位置分页
有一种绝对位置分页的方法可作为你的备选项。它没有使用“从头开始第几条”的描述方法,而是指定了某个 ID 之前,或者日期之前的条件。这样我们查询数据库时可以使用范围匹配来查询出数据返回到客户端,避免了使用数据库的分页功能从而造成性能隐患。
示例 |
---|
sample.api.com/v1/user/register-before/2017-08-15T00:00:00 |
客户端请求查询 17 年 8 月 15 日之前注册的所有用户的信息列表。服务器端对于这样的请求使用
select * from user where time < 2017-08-15T00:00:00
即可轻松解决问题。从而避免了从 1 开始计数的扫表操作。
查询参数与 URI 如何取舍
下面这两个示例,你觉得哪个更好?嘘,先别说话。用心去感受。
示例 | |
---|---|
A | sample.api.com/v1/user/1198723 |
B | sample.api.com/v1/user?userid=1198723 |
无论你选择了 A 或者 B,希望你都是出自于理性思考而非感觉。 A 与 B 的区别在于 A 的设计将查询参数放到了 URI,而 B 的设计保持了查询参数的原本位置。可能在目前市面上 B 的设计更为常见一点。
类似于这样的查询参数与 URI 取舍的问题,我们最好采用下面这 2 个决策依据帮助我们进行判断。
参数是否表示唯一的资源所需的信息是否可以省略
结合参数若能够定位到互联网上唯一的资源,则应该将查询参数放到 URI 中。这是基于 URI 的根本含义:统一资源定位符含义来进行设计的。而若查询参数无法配合 URI 定位到唯一的资源甚至与资源无关的话,则应该放到参数位置。
所以,A 的设计在学术理论上更优于 B。这代表着从今天开始,你对于查询接口的设计水平已经超越了贵公司大部分工程师。
响应的设计
相对查询来说响应的设计更考究工程师的细节功底。
性别的设计
性别有两大主流设计。
使用数值来标识对应的性别。使用 male、female。
实际上无论使用哪种都是可以接受的。但是!你需要知道 14 年 facebook 上线的新系统里性别的可选项目增加到了 50 种以上。假如你负责设计的接口是供一个 SNS 系统而使用的话,则需要考虑保证社交系统中关于性别的多元化的可能性。
所以使用 2 比 1 的可扩展性更强一些。
日期的格式
在 Http 协议的定义中,http header 中通常会使用 UTC 来标识 Http 的时间。因此在设计 api 时,也推荐使用时区“+00:00”。相对于 epochtime 我更喜欢 RFC 3339 的日期格式,他更易读并且能够体现出时区的概念。而若要使用 UTC 时间的话,则在后面加上 Z 标识即可。
2017-11-11T13:00:12+00:00
2017-11-11T13:00:12Z
2017-11-11T10:00:00-00:00
-00:00 时区标识时区不明。虽然这种表示很少见,但是正因为少见所以需要重点掌握。
整数
计算机中 int 占用 4 个字节。如果使用 unsigned 无符号类型的话,则能表示 0 到 4294967295.如果你设计的是一个 SNS 网站,一定要避免用 int 作为返回值,来标识用户的 id 或者其他信息,因为这点空间是完全不够的。将信息量较大的数据统统使用 string 来表示吧。
别名的设计
试想,某日你接到了一个用户模块的设计,其中包括了一个功能:即登录用户可以通过 api 查询用户的个人详细信息。大部分工程师针对此需求很容易设计出以下的代码。
示例 |
---|
https://sample.api.com/v1/user/info/1987203 |
response
{
"userId": "",
"userName": "",
"userPassword": ""
}
回想一下你是不是设计过这样的端点?
很可惜,你的代码实际上引发了一个巨大的安全隐患。用户可以传入任何用户的 id 信息查询到对应用户的隐私数据,造成用户信息泄露。
针对这样的需求,在设计此类 api 时应该使用别名来代替用户 id 的传入,防止混淆使用一个接口完成查询其他用户与自身详情两个功能导致信息泄露的问题。
示例 |
---|
https://sample.api.com/v1/user/info/me |
像上述示例中的 api,客户端访问以 me 或者 self 单词结尾的端点,服务器获取到请求后,根据用户 access zoken 查询用户的详细信息返回给客户端使用。
而针对其他用户的信息获取我们采用
示例 |
---|
https://sample.api.com/v1/user/info/1987203 |
不同的端点去解决需求。通过使用这样的设计方式,让这两个接口的开发被拆分开来,从而最大限度的降低了混淆使用获取其他用户信息 api 与获取自身用户信息 api 接口的可能性。更容易防止误将其他用户的个人信息对外公开的 bug 发生。
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。