《你不知道的 JAVA》💘 什么是好的 Web Api 设计 (第二章)

131 阅读6分钟

学会这款 全新技术的 Java 脚手架 ,从此面试不再怕!

Trump-mjga-logo_cn.png

查询的设计

依据上一章节内容的内容,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 的所有用户列表。

来看看微软是如何设计搜索引擎的查询参数的。

示例
www.bing.com/search?q=ur…

当 uri 中存在无法直接使用的字符时,会用一种百分号编码的方式对字符进行处理。例如%E3%81 等。你可能会认为使用 ASCII 字符集就可以高枕无忧了。但是应该各位注意,%、&、+等字符也需要百分号编码。而空格符号在百分号编码中会被编码为+号。

查询参数与 URI 如何取舍

下面这两个示例,你觉得哪个更好?

示例
Asample.api.com/v1/user/1198723
Bsample.api.com/v1/user?userid=1198723
A 与 B 的区别在于 A 的设计将查询参数放到了 URI,而 B 的设计保持了查询参数的原本位置。可能在目前市面上 B 的设计更为常见一点。
类似于这样的查询参数与 URI 取舍的问题,我们最好采用下面这 2 个决策依据帮助我们进行判断。
  • 参数是否表示唯一的资源所需的信息
  • 是否可以省略 如果参数能够定位到互联网上唯一的资源,则应该将查询参数放到 URI 中。这是基于 URI 的根本含义——统一资源定位符含义来进行设计的。 而若查询参数无法配合 URI 定位到唯一的资源甚至要做的事和资源本身无关的话,则应该放到查询参数位置。

所以在满足上述条件的情况下, A 在学术上优于 B。

查询的别名

某日你接到一个新需求:已登录的用户可以通过 api 查询用户的个人详细信息。针对此需求很容易设计出以下的代码:

示例
sample.api.com/v1/user/inf…
response
{
  "userId": "",
  "userName": "",
  "userPassword": ""
}

回想一下你是不是设计过这样的端点?很可惜,你的代码实际上引发了一个巨大的安全隐患。用户可以传入任何用户的 id 信息查询到对应用户的隐私数据,造成用户信息泄露。

针对这样的需求,在设计此类 api 时应该使用别名来代替用户 id 的传入,防止混淆使用一个接口完成查询其他用户与自身详情两个功能导致信息泄露的问题。

示例
sample.api.com/v1/user/inf…
像上述示例中的 api,客户端访问以 me 或者 self 单词结尾的端点来获取个人信息。而针对其他用户的信息获取我们才采用下面这样的端点设计。
示例
sample.api.com/v1/user/inf…

响应的设计

相对查询来说响应的设计更考究工程师的细节功底。

性别的设计

性别有两大主流设计。

  1. 使用数值来标识对应的性别。
  2. 使用 male、female。 实际上无论使用哪种都是可以接受的。但是!你需要知道 14 年 facebook 上线的新系统里性别的可选项目增加到了 50 种以上。(而川普上台让这一切又打回了原型)所以如果考虑多元化那使用 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 或者其他信息,因为这点空间是完全不够的。将信息量较大的数据统统使用 long 甚至是 string 来表示。

分页查询的设计

相对位置分页

分页查询的重点在于分页参数,通常来说 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 开始计数的扫表操作。

写在最后

  • 我是 Chuck1sn,一个长期致力于现代 Jvm 生态推广的开发者。
  • 您的回帖、点赞、收藏、就是我持续更新的动力。
  • 举手之劳的一键三连,对我来说是莫大的支持,非常感谢!
  • 关注我的账号,第一时间收到文章推送。

PS:以上所有代码示例你都可以在 Github 仓库中找到。如果有帮助,请顺手点一个 Star 这对我是很大的鼓励。谢谢!