【译】如何构建一个好的API

2,110 阅读14分钟

原文:www.stxnext.com/blog/how-to…

每个人都想要一个 API。API 大约在 20 年前开始流行。Roy Fielding 于2000年在其博士论文中引入了 REST 一词。同一年,Amazon,Salesforce 和 eBay 向世界各地的开发人员介绍了他们的 API,从而永远改变了我们构建软件的方式。

在使用 REST 之前,Roy Fielding论文中的原理被称为“ HTTP 对象模型”,您很快就会明白为什么这很重要。

在继续阅读时,您还将了解 如何确定您的 API 是否成熟,优质 API 的主要品质是什么,以及为什么在构建API时应着重于适应性

RESTful 体系结构的基础

REST 代表代表性状态转移,它一直是服务 API 的圣杯,最早由 Roy Fielding 在其论文中定义。这不是构建 API 的唯一方法,但是由于其受欢迎程度,即使是非开发人员也知道的那种标准。

RESTful软件具有六个关键特征:

  1. 客户端-服务器(Client-Server)架构
  2. 无状态(Statelessness)
  3. 可缓存性(Cacheability)
  4. 分层系统(Layered system)
  5. 按需代码(Code on demand)(可选)
  6. 统一的接口

但这对于日常使用来说太理论化了。 我们希望有更多可行的方法,那就是 API 成熟度模型。

Richardson 成熟度模型

该模型 Leonard Richardson 开发,将 RESTful 开发的原理结合为四个易于遵循的步骤。

您在模型中越高,就越接近 Roy Fielding 定义的 RESTful 原始概念。

第 0 级:POX 沼泽(the swamp of POX)

第 0 级 API是一组简单的 XML 或 JSON 描述。在引言中,我提到在 Fielding 论文发表之前,RESTful 原理被称为“ HTTP 对象模型”。

这是因为 HTTP 协议是 RESTful 开发中最重要的部分。 REST围绕使用尽可能多的HTTP固有属性的想法展开。

在第 0 级,您不会使用任何东西。您只需构建自己的协议并将其用作专有层即可。这种架构称为远程过程调用(Remote Procedure Call,RPC),非常适合远程过程/命令。

通常,您可以调用一端来接收一堆 XML 数据。SOAP 协议就是一个例子:

另一个很好的例子是 Slack API。 它有点多样化,有多个端点,但仍然是 RPC 样式的 API。 它公开了 Slack 的各种功能,而中间没有任何附加功能。 下面的代码使您可以将消息发布到特定频道。

即使按照 Richardson 的模型是 0 级 API,也并不意味着它很糟糕。只要它可用并适当满足业务需求,它就是一个很棒的 API。

第 1 级:资源(Resources)

要构建 1 级 API,您需要在系统中找到名词,并通过不同的 URL 公开它们,如以下示例所示。

/api/books 会将我带到常规书籍目录。 /api/profile 会将我带到那些书的作者的个人资料-如果只有一本。 为了获得资源的一个特定实例,我向 URL 添加了一个 ID(或其他引用)。

我还可以将资源嵌套在 URL 中,以表明它们是按层次结构组织的。

回到 Slack 示例,下面是第 1 级 API 的样子:

URL 改变了。我们使用 /api/channels/general/messages,而不是 /api/chat.postMessage

信息的 “channel” 部分已从正文移至URL。 确切地说,使用此API,您可以期望将一条消息发布到 general 渠道。

第 2 级:HTTP 动词(HTTP verbs)

第 2 级 API 利用 HTTP 动词添加更多含义和意图。这些动词有很多,我只使用一个基本子集:PUT / DELETE / GET / POST。

对于这些动词,我们期望包含它们的URL有不同的行为:

  • POST:创建新数据
  • PUT:更新现有数据
  • DELETE:删除数据
  • GET:查找特定 ID 的数据输出,获取资源(或整个集合)

或者,使用前面的 /api/books 示例:

“安全”和“幂等”是什么意思?

一种“安全”的方法永远不会更改数据。REST 建议 GET 仅获取数据,因此它是以上集合中唯一安全的方法。无论您调用基于 REST 的 GET 方法有多少次,它都绝不应更改数据库中的任何内容。但这不是动词固有的,而是关于如何实现的,因此您需要确保它能起作用。所有其他方法都会以不同的方式更改数据,因此不能随意使用。在 REST 中,GET 既安全又幂等。

“幂等”方法在很多用途下都不会产生不同的结果。根据 REST,DELETE 应该是幂等的:如果您删除一次资源,然后再次调用同一资源的 DELETE,则不应更改任何内容,资源应该已经消失了。POST 是 REST 规范中唯一的非幂等方法,因此您可以多次发布相同的资源,并且会重复。

让我们重新看一下 Slack 示例,看看如果我们在其中使用 HTTP 动词执行更多操作,它将是什么样子。

我们可以使用 POST 将消息发送到 general 频道。我们可以使用 GET 从 general 频道获取消息。我们可以使用 DELETE 删除具有特定 ID 的消息,这很有趣,因为消息没有绑定到特定的渠道,所以我可能想设计一个单独的 API 来删除消息。这个例子说明了设计 API 并不总是那么容易。有很多选择和权衡取舍。

第 3 级—HATEOAS

还记得没有图像的纯文字电脑游戏吗?您只有很多文字,其中包含您的位置以及接下来的操作的描述。要取得进展,您必须输入选择。这就是 HATEOAS。

HATEOAS 代表“作为应用程序状态引擎的超媒体”(Hypermedia as the Engine of Application State)

当您拥有 HATEOAS 时,只要有人使用您的 API,他们就可以看到他们可以使用它做的其他事情。HATEOAS 回答了“我从这里可以去哪里?”这一问题。

但这还不是全部。HATEOAS 还可以为数据关系建模。我们可以提供资源,并且 URL 中没有嵌套的作者,但是我们可以发布链接,因此,如果有人对作者感兴趣,他们可以去那里探索。

它不像其他级别的成熟度模型那样受欢迎,但是有些开发人员会使用它。一个例子就是吉拉。以下是他们的搜索API的一部分:

它们嵌套了指向您可以探索的其他资源的链接,以及此问题的过渡列表。由于顶部的“expand”参数,它们的 API 非常有趣。它使您可以选择完整的内容而非链接。

使用 HATEOAS 的另一个示例是 Artsy。他们的 API 非常依赖 HATEOAS。他们还使用 JSON Plus 调用规范,该规范强制添加了构造链接的特殊约定。以下是分页的示例,这是使用 HATEOAS 的最酷的示例之一。

您可以提供指向下一页,上一页,第一页,最后一页以及您认为必要的其他页面的链接。这简化了 API 的使用,因为您不需要向客户端添加 URL 解析逻辑,也不需要添加页码的方法。您只需让客户端准备使用已经结构化的链接即可。

什么才是好的API

Richardson 的模型非常重要,但这并不是一个好的API的全部。 其他重要素质是什么?

错误/异常处理

我期望从我使用的API中获得的基本要点之一是,需要一种明显的方法来判断是否存在错误或异常。我需要知道我的请求是否已处理。

瞧,HTTP也有一个简单的方法:HTTP状态码。

管理状态码的基本规则是:

  • 2xx 表示 OK
  • 3xx 表示您的公主在另一座城堡中:您正在寻找的资源在另一处
  • 4xx 表示客户端错误
  • 5xx 表示服务器错误

至少,您的 API 应该提供 4xx 和 5xx 状态码。5xx 有时会自动生成。例如,客户端向服务器发送了一些东西,这是一个无效的请求,验证有缺陷,问题出在代码上,我们有一个例外-它会返回 5xx 状态代码。

如果您想使用特定的状态代码,则会发现自己想知道“哪种状态码最适合这种情况?”。这个问题并不总是容易回答。我建议您使用 RFC,其中指定了这些状态码,它们比其他来源提供了更广泛的解释,并告诉您这些代码何时合适之类的信息。幸运的是,在线上有很多资源可以帮助您选择,例如 Mozilla的HTTP状态代码指南

文档

出色的 API 拥有出色的文档。文档的最大问题通常是随着 API 的增长需要有人对其进行更新。一个不错的选择是不与代码分离的自更新文档。

例如,注释与代码没有关联。当代码更改时,注释保持不变就会变得过时。这可能比没有注释更糟糕,因为一段时间后,注释将提供错误的信息。注释不会自动更新,因此开发人员需要记住将其与代码一起进行维护。

自更新的文档工具可以解决此问题。Swagger 是一种流行的工具,它是基于 OpenAPI 规范构建的工具,可轻松描述您的 API。

Swagger 的最酷的是它是可执行的,因此您可以使用该 API 并立即查看它的功能以及其变化方式。

要将自更新添加到 Swagger,您需要使用其他插件和工具。在 Python 中,大多数主要框架都有插件。它们生成有关 API 请求结构的描述,并定义输入哪些数据和输出哪些数据。

如果您不想要 Swagger,而更喜欢简单的东西怎么办?一种流行的替代方法是 Slate:一种静态 API,您可以在你的 URL上构建并公开该 API。

两者之间也值得推荐的是 widdershinsapi2html 的组合。它可以让您根据Swagger 的定义生成类似 Slate 的文档。

可缓存性

在某些系统中,可缓存性可能并不重要。您可能没有太多可缓存的数据,所有内容一直都在变化,或者您的流量不多。

但是在大多数情况下,可缓存性对于良好的性能至关重要。这与 RESTful API 有关,因为 HTTP 协议与缓存有很多关系,例如 HTTP 标头可让您控制缓存行为。

如果您有注册表或值存储区来保存数据,则可能要在客户端或应用程序中缓存内容。但是HTTP实质上允许您免费获得良好的缓存,因此,如果可能的话-不要放弃免费午餐。

另外,由于缓存是 HTTP 规范的一部分,因此许多参与 HTTP 的事物都将知道如何缓存事物:浏览器支持本机缓存,浏览器与客户端之间的其他中间服务器也支持缓存。

发展中的 API 设计

一般而言,构建 API 和现代软件最重要的部分是适应性。没有适应性,开发时间就会变慢,并且在合理的时间内发布功能变得更加困难,尤其是当您面临最后期限时。

“软件体系结构”在不同的上下文中具有不同的含义,但现在就采用以下定义:

软件体系结构:规避决策的行为/技巧,可防止将来发生变化。

考虑到这一点,当您设计软件并必须在具有相似优势的选项之间进行选择时,应始终选择更具前瞻性的选项。

好的做法还不够。以正确的方式构建错误的东西也不是你所希望的。最好采用成长的心态,接受改变是不可避免的,尤其是在您的项目要继续成长的情况下。

为了使您的API更具适应性,要做的关键之一就是使您的 API 层保持“薄”。应该降低真正的复杂性。

APIs 不应规定实现方式

发布公共 API 后,它就完成了,是不可变的,你不能再碰它。但是,如果您别无选择,只能提交一个设计怪异的API,该怎么办?

您应该始终寻找简化实现的方法。与构建另一个 API 并将其称为 v2 相比,有时使用特殊的 HTTP 标头控制 API 响应格式是一种更精简的解决方案。

API只是抽象的另一层。它们不应该规定实施方式。您可以应用几种开发模式来避免此问题。

API 网关

这是一个类似外观的开发模式。如果您将一个整体拆分成一堆微服务,并想向世界展示一些功能,则只需构建一个像外观的 API 网关即可。

它将为不同的微服务(可能具有不同的API,使用不同的错误格式等)提供统一的接口。

后端 for 前端

如果必须构建一个API来满足大量不同的客户端,则可能会很困难。为一个客户端做的决定将影响其他客户端的功能。

后端 for 前端是指:如果您有喜欢不同API的不同客户端,比如 GraphQL 这样的移动应用程序,只需为它们构建。

仅当您的 API 是抽象层并且很薄时,此方法才有效。如果它耦合到您的数据库,或者它太大,逻辑太多,您将无法执行此操作。

GraphQL vs RESTful

GraphQL有很多炒作。有点像新手,但已经吸引了很多粉丝,以至于一些开发人员声称它将取代 REST。

尽管 GraphQL 与 RESTful 规范相比要新得多,但它们具有许多相似之处。GraphQL 的最大缺点是:可缓存性必须在客户端或应用程序中实现。尽管有内置了缓存功能的客户端库(例如 Apollo),但是它仍然比使用 HTTP 提供的几乎免费的缓存能力更难。

从技术上讲,就 Richardson 模型而言,GraphQL 为 0 级,但它具有良好的 API 特质。您可能无法使用多种 HTTP 功能,但是 GraphQL 是为解决特定问题而构建的。

GraphQL 的杀手级用途是聚合不同的 API,并将它们暴露为一个 GraphQL API。

GraphQL 确实在 underfetching 和 overfetching 的问题上表现很好,这是 REST API 难以解决的问题。两者都会影响性能:如果 underfetch,您将无法有效使用 API​​ 调用,因此您必须进行很多操作。当您 overfetch 时,您的请求将导致不必要的数据传输,这是浪费带宽。

下表中 REST 与 GraphQL 的比较很好地总结了一个好的 API 的最重要的品质。

RESTfull + 扩展GraphQL
清楚的数据表示资源schema
操作资源 + HTTP 动词Queries & mutations & subscriptions
清楚地告知是否有错误/异常状态HTTP 状态码“errors”属性
可发现性 & 可能的导航HATEOASSchema,graphql
[可执行的]文档Swagger / Slate / 其他
可缓存性HTTP 头只能应用程序或客户端层

您需要清楚地表示数据:RESTful 以资源的形式为您提供数据。

您需要一种方法来显示可用的操作:RESTful 通过将资源与 HTTP 动词结合在一起来实现。

需要有一种方法来确认是否存在错误/异常:HTTP 状态代码会执行此操作,可能还会通过响应进行解释。

能够发现可能的导航:在 RESTful 中,HATEOAS 提供这个功能。

拥有出色的文档非常重要:可执行的,自更新的文档,而这超出了 RESTful 规范。

最后但并非最不重要的一点是,除非您的特定情况表明没有必要,否则优质的 API 应该具有可缓存性。

REST 和 GraphQL 之间的最大区别是它们处理可缓存性的方式。当您以 REST 方式构建 API 时,基本上可以免费获得 HTTP 缓存能力。如果选择 GraphQL,则需要操心将缓存添加到客户端或应用程序。