查看原文
其他

如何设计优雅的API

盛荣 译 高可用架构 2019-11-29

导读:互联网的发展、Web 应用的整合、移动端的繁荣都离不开 Web API 这一幕后英雄的鼎力相助,本文就如何优雅的设计 Web API 从作者自我经验方面做出介绍,并通过举例对它们进行深入剖析, 最后总结出适用于 Web API 各个方面的普遍性规律。


一、设计优美的 Web API 


设计优美的 Web API 的重要性 


本文的主题是讨论如何设计优美的 Web API 。这里“优美”一词的含义同 “代码之美”中“美”的含义相同,指的是进行了周密的斟酌、浅显易懂的整理,并 剔除了无用之处等,它体现了完成度的高低。但是,这里不免要问为什么 Web API非要设计得优美不可呢?简而言之,就好比问为什么代码非要写得优美一样,每个编写代码的开发人员估计都有各自的答案,接下来让我们就这个话题进行深入的探究。 


首先列举一下 Web API 需要设计得优美的几个理由:


  • 设计优美的 Web API 易于使用

  • 设计优美的 Web API 便于更改

  • 设计优美的 Web API 健壮性好

  • 设计优美的 Web API 不怕公之于众


下面让我们逐一阐述。 


设计优美的 Web API 易于使用 


首先,很多情况下设计 Web API 并非只是为了自己使用。且不说广泛公开的Web API,即使是某些移动应用的 API,服务器端和客户端分别由不同开发人员负 责的情况也很多。在这种情况下,根据 Web API 设计的好坏,API 的易用性也会大 相径庭,而这对开发周期、开发人员在开发期间所承受的压力大小等都会带来巨大影响。


设计 API 的目的原本是让更多的用户能够简单地使用,如果公开了难以使用的API,那么公开 API 的意义也就不大了。


设计优美的 Web API 便于更改 


Web 服务以及 Web 系统几乎是每时每刻都在变化的,也就是说,保持公开时的 状态连续使用两三年的情况非常罕见。因此,当我们的服务发生变化时,作为其接口的 API 显然也不得不随之改变。


但是,公开的 API 在很多情况下会被与自己无关的第三方调用,如果这时突然 变更 API 的设计规范,很有可能造成这些第三方开发的系统、服务等一下子变得不可用。这无疑是 API 提供者需要极力避免的情况。


移动应用的情况下,什么时候更新应用往往由用户自己掌控,即使将客户端应 用更新到最新版本,也依然存在使用老版本的用户。此时如果突然变更 API 的设计规范,就会使得老版本的应用突然无法使用。而“设计优美的 API”的含义中就包括了更改 API 时尽量不影响正在使用的用户 这一层意思。


设计优美的 Web API 健壮性好 


同普通的 Web 站点一样,Web API 在很多情况下也通过网络提供服务,由于谁 都能够访问 Web API,因此必然会涉及安全问题。由于 Web API 同 Web 站点一样使 用了 HTTP 协议,因此会面临同 Web 站点一样的安全问题,除此之外,开发人员还 需考虑 API 特有的安全问题。只有充分应对了以上这些问题,才能称得上是设计优美的 API。 


设计优美的 Web API 不怕公之于众 


Web API 同一般的 Web 站点、Web 服务不同,主要面向开发人员。众所周知, 开发人员往往喜欢评价其他开发人员写的代码、接口等成果,因此,如果公开的API 在其他开发人员眼中显得丑陋、没有美感的话,该服务提供者的技术水平也会受到质疑。


当然,如果所提供的服务魅力无穷,或者让人不得不使用的话,即使 API 设计 得再丑,可能也会有人使用,但丑陋的 API 设计会给其他不相关的地方带来影响。 例如优秀的开发人员一般不太愿意加入技术水平较差的公司或者团队,所以很有可能只是因为 API 设计得差而导致公司无法招到优秀的技术人员。优秀的服务背后一般都需要有出色的技术团队支撑(但这并不意味着拥有了出色的技术团队就一定会有优秀的服务),而如果没有出色的技术团队,长此以往就可能导致公司发展缺乏后劲,始终无法创造出优秀的服务。


二、开发方便更改设计的 Web API 


Web API 和普通的 Web 服务一样,并不是说发布后所有工作便就此结束了,而 是必须始终保持公开状态,否则就没有意义。在持续公开API 的过程中,我们可能会遇到发布之初没有想到的 API 使用方法,或不得不为其增添新的功能等,有时甚至还会因为某些原因而不得不终止公开API。接下来我们就来讨论一下与 API 更改及废除相关的各种问题。 


方便更改设计的重要性 


Web API 承担着应用程序接口的角色。应用发布后,往往难以保持一成不变。 随着时间的推移,应用会根据各种情况不停地发生变更,比如强化某些功能、修正bug,或者废除某些功能等。这时,Web API 作为应用面向其他应用的接口,有时也 必须随之进行相应的变更。当然,如果在线服务只是外观上发生少许变化,或者变 化只对内容有影响但没有影响数据格式,也就没有必要去更新 API 了。但如果是数 据格式发生了变化,或者需要在信息检索时添加新的查询参数等,就必须对 API 进 行相应的变更。


比如,当以文本形式返回的数据内容变得更加详细时,或由于内部算法的更新 使得搜索精度提高时,虽然访问 API 得到的数据内容发生了变化(大多数情况下会 有所改善),但数据格式却没有改变,这时就没有必要更改 API 的设计规范了。在 Web 服务运营的过程中,类似这种数据格式没有变化但内容却有所改善的情形每天都在上演。


另一方面,在数据格式自身发生变化时,比如原本以数值形式公开的 ID 信息变为了字符串形式,废弃了以往返回数据中包含的“关联信息推荐”,原来分别输出年 月日信息的方式变成了合并为一个整体输出等,API 响应数据的格式就一定会更改。 另外,在获取数据时,如果从使用字符串指定分类的方式变成了使用 ID 来指定,或 者在搜索时增加了性别条件,亦或用户注册时的邮箱地址变成了可选项等,这时就 必须更改 API 的请求参数。如果发现 API 的设计存在安全漏洞等致命问题,或许还 需要修改 API 的内容。


但是,类似这种 API 的变更非常棘手,因为这会给使用 API 的外部系统或服务 带来巨大影响,而且很难评估影响究竟有多大。 


通过版本信息来管理 API 


最容易想到的方法是尽量不去修改已公开发布的 API,但这样就很难进行在线 服务的改善工作,所以实际上这一方法是行不通的。新 API 只需通过某种新的访问 方式公开即可,比如使用不同的端点,或使用添加了其他参数的 URI 等。


换言之,对于使用旧方式访问的客户端,和之前一样发送数据即可;而对于使 用新方式访问的客户端,则要返回更新后的数据。也就是说,我们可以同时提供多个版本的 API。 


如图:新 API 以新形式对外发布,旧 API 依然予以保留 


让新旧两个版本以上的 API 共存的方法有很多,其中最容易理解的实现方式是使用完全不同的 URI 来发布,如下所示:


❖旧的 URI

http://api.example.com/users/123

❖新的 URI

http://newapi.example.com/users/123


如此一来,那些使用早期发布的旧版本 API 的用户就可以忽视 API 的更新,从 而继续使用之前版本的 API,并在合适的时机向新版本迁移,而新用户则可以从一 开始就直接使用新版本的 API。以上示例作为例子来说非常容易理解,但如果是实际的 URI,这样的实现方式则难以奏效(只能作为例子展示),而且这里使用 new 来命名也很有问题。因为只有在刚发布的阶段才能称为“新”,倘若未来出现了更新的 版本,那么又该如何命名呢?日本有很多以“新”来命名的案例,如新快速、新千岁 机场等,虽然它们就现在而言已经不新了,但名称里依然带有“新”字。API 的情况 也同样如此,当需要更新其版本时,该使用怎样的 URI 来标识最新版本的 API 呢?


和软件的版本管理一样,我们也可以使用版本信息来对 API 的版本进行管理, 这是最容易理解的一种方式。当客户端访问 API 时,服务器端通过某种方式告知其API 的版本信息,就可以做到让多个版本的 API 共存。如此一来,版本 2 新于版本 1就不言自明了。当后续又有新版本发布时,只需使用版本 3 来公开即可。


在 URI 中嵌入版本编号 


首先看一个在 URI 中嵌入版本编号的范例。


❖ Tumblr

http://api.tumblr.com/v2/blog/good.tumblr.com/info


上面是 Tumblr 的 API。该 API 的路径开头有一个 v2,这便是 API 的版本编号,从中不难得知该 API 是版本 2。Tumblr 曾 发布过版本 1 的 API (http://www.tumblr.com/docs/en/) 但该版本的 API 现在已 被废除 


❖ Tumblr 版本 1

http://www.davidslog.com/api/read 


我们发现版本 1 的 URI 里并没有添加版本编号。虽然 Tumblr 在 2012 年公开的 版本 2 的 API 中使用了在 URI 里嵌入版本编号的方式,但早期发布的版本 1 并没有 遵循这一惯例。在公开发布的 API 中,类似 Tumblr 这样从某个版本开始引入版本 编号的情况并不少见,比如 Twitter 的 API 从版本 1.0 升级至 1.1 时,就在新版本的URI 里添加了版本编号(下表)。另外,在 API 升级后,1.1 版本的 API 所使用的 端点也不同于之前的版本 1(旧版本 API 端点在短期内仍可继续使用)。



在 URI 路径的开头嵌入 API 版本编号这一方法最为常见,也最为易懂。


但是在具体处理时,Twitter 和 Tumblr 则略有不同:Tumblr 在版本信息前添加 了字母 v,并使用了 1、2 这样的主版本(Major Version)编号;而 Twitter 却使用了 类似 1.1 这样包含次版本(Minor Version)编号的版本命名方式。


那么哪种方式最易懂呢?让我们来看一下其他几个 API 的示例(如下表)。



可以发现,根据为 API 添加版本编号时是否使用字母 v,可以将以上示例大致 分为两类。具体哪种方式更好只是个人偏好的问题,笔者个人比较喜欢添加字母 v的方式。因为 v 表示版本,添加 v 之后可以让人一目了然,易于理解。还有些服务 采用了特立独行的方式来描述 API 的版本信息,如 Gnavi、CrunchBase 等。虽然这 么做问题不大,但如果没有特殊的偏好,则没有必要使用这样的方式 


终止提供 API 


通过版本来区分 API,这样在提供新版本 API 时就不会给那些依然在使用旧版 本的用户带来巨大影响,不过持续维护多个版本无疑会增加整个服务的运营成本。 即使你已决定不再继续更新旧版本的 API,一旦发现安全问题等,也不得不进行更新。 另外,随着后端功能的更新(如数据库 schema 的更改等),为了保持前端接口不变, 有时也不得不更新与之相关的 API 代码。


如前所述,为了减轻维护负担,应尽可能地不去频繁升级 API 版本,而是要在 确保向下兼容的同时进行相应的更改。但随着在线服务长年累月地运营,难以保持 向下兼容的更改会越来越多。考虑到成本问题,继续维护和支持多个版本的 API 是 不现实的,这种情况下就需要考虑选择适当时机去废除旧版本的 API。


另外,当旧版本的 API 在结构上存在安全隐患(如个人信息没有经过加密直接 被发送、能够获取他人的信息等)等问题时,有时也需要尽快结束旧版本 API 的公开。


当需要终止对外提供旧版本的 API 时,如果不进行任何通告就突然终止,无疑 会导致依然在用旧版本 API 的用户忽然变得无法访问,进一步将会导致应用程序出 错或部分 Web 页面(或全部)无法显示等问题,后果非常严重。


因此,在终止 API 服务时,尤其是在终止那些已被广泛使用的 API 时,我们需 要事先公布预计终止的时间等信息,以便用户在此之前采取应对措施。这项工程非 常复杂,但对 API 的公开及运营来说非常重要。 


案例学习:Twitter 废除旧版本的 API 


Twitter 于 2012 年公开发布了版本 1.1 的 API,与此同时开始了废除版本 1.0 的 工作。Twitter 废除旧版本 API 的做法很有参考价值,下面就让我们按时间顺序来回 顾一下整个事件的经过。


首先,Twitter 在 2012 年 8 月宣布即将发布版本 1.1 的 API。当时 Twitter 称会 在几周后发布版本 1.1,并在 6 个月内废除版本 1.0。2012 年 9 月,版本 1.1 的 API正式发布。2012 年 10 月,Twitter 更新了版本 1.0 的端点信息,要求用户访问时必 须使用嵌入了版本编号的新 URI,不然将无法正确访问。


2013 年 3 月,Twitter 举行了名为 Blackout Test 的测试,暂停提供版本 1.0 的API,使用户无法访问。这项测试陆续进行了多次,直到旧版本的 API 被废除。


Twitter 于 2013 年 3 月宣布将在 5 月 7 日正式废除版本 1.0 的 API ,但随后因 为再次追加了 Blackout Test,废除日期延迟到了 6 月 11 日 。最终 Twitter 于 6 月 11日废除了版本 1.0。


由于 Twitter 用户众多,在正式废除版本 1.0 的 API 之前,很多博客及新闻媒体 都对此进行了相关报道,可谓做到了广而告之。另外,像这种持续通告及 Blackout Test 的做法等都非常具有参考价值。 


三、设计优雅API的一些经验要点总结


  • 如果尚未公开 Web API,则应立刻考虑公开。

  • 设计优美的 Web API。

  • 不用过分拘泥于 REST 一词。

  • 最大限度地减少 API 版本的更新频率,注意向下兼容性。

  • 在 URI 中嵌入 API 版本的主版本编号。

  • 停止提供 API 服务时不能立刻终止,至少需要继续公开 6 个月。

  • 最大程度地利用 HTTP 协议规范,最小程度地使用私有规范。

  • 使用合适的状态码。

  • 返回恰当的、尽可能通用的媒体类型。

  • 返回便于客户端执行恰当的缓存的信息。

  • 使用 JSON 或者和目的一致的数据格式。

  • 不要进行不必要的封装。

  • 响应数据尽可能使用扁平化结构。

  • 各个数据的名称应简洁易懂,并恰当地使用单复数。

  • 错误格式应统一,使客户端能够机械地理解错误的详细信息

  • 设计容易记忆、功能一目了然的端点。

  • 使用合适的 HTTP 方法。

  • 选择合适的英语单词,注意单词的单复数形式。

  • 使用 OAuth 2.0 进行认证。 


本文节选自人民邮电出版社《Web API的设计与开发》一书,由图灵授权,感兴趣的读者可以在各大网店购买。


54 个架构案例  49 位作者   2 年打磨

高可用架构』第 1 卷  10 月上市

点击 阅读原文 抢先预订


高可用架构

改变互联网的构建方式

长按二维码 关注「高可用架构」公众号

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存