RESTful API 设计最佳实践:打造高性能接口
RESTful API 设计最佳实践:打造高性能接口
RESTful API设计是现代软件开发中的重要技能,它不仅关乎技术实现,更涉及架构设计和最佳实践。本文将从RESTful架构基础出发,深入探讨Richardson成熟度模型,并结合实际案例,为你呈现打造高性能API的关键要素。
RESTful 架构基础
REST,代表表现层状态转移(Representational State Transfer),长久以来一直是API服务的圣杯,最初由Roy Fielding在其博士论文中定义。尽管它不是构建API的唯一方法,但由于其广泛的普及,即使是非开发者也对其有所了解。
RESTful软件具有六个关键特征:
- 客户端-服务器架构
- 无状态性
- 可缓存性
- 分层系统
- 按需代码(可选)
- 统一接口
但这些还太理论化了,我们需要一些更具操作性的内容,那就是API成熟度模型。
Richardson 成熟度模型
由Leonard Richardson开发,该模型将RESTful开发的原则合并为四个易于遵循的步骤。
等级 0:POX的沼泽
一个0级API是一组简单的XML或JSON描述。在介绍中,我提到在Fielding的论文之前,RESTful原则被称为“HTTP对象模型”。
这是因为HTTP协议是RESTful开发的最重要部分。REST围绕尽可能多地使用HTTP的固有属性的理念展开。
在0级,你根本不使用这些东西。你只是构建自己的协议并将其用作专有层。这种架构被称为远程过程调用(RPC),它非常适合远程过程/命令。
你通常有一个端点来接收一堆XML数据。例如SOAP协议:
另一个很好的例子是Slack API。它稍微多样化一些,有几个端点,但它仍然是RPC风格的API。它暴露了Slack的各种功能,中间没有增加任何功能。以下代码允许你向特定频道发布消息。
尽管它是根据Richardson的模型是0级API,但这并不意味着它不好。只要它可用并能正确服务于业务需求,它就是一个很棒的API。
等级 1:资源
要构建一个1级API,你需要在系统中找到名词,并通过不同的URL暴露它们,如下例所示。
/api/books将我带入通用书籍目录。/api/profile将我带入这些书的作者的个人资料(如果只有一个的话)。要获取资源的第一个具体实例,我在URL中添加ID(或其他引用)。
我还可以在URLs中嵌套资源,并显示它们是如何层次化组织的。
回到Slack的例子,这是它作为1级API的样子:
URL发生了变化;现在我们有了/api/channels/general/messages代替/api/chat.postMessage。
“channel”部分的信息已从正文移到URL中。这确实表明使用这个API,你可以期待将消息发布到general频道。
等级 2:HTTP动词
一个2级API利用HTTP动词添加更多的含义和意图。这些动词有很多,我只使用一小部分基本的:PUT / DELETE / GET / POST。
使用这些动词,我们期望含有它们的URLs展现不同的行为:
- POST—创建新数据
- PUT—更新现有数据
- DELETE—移除数据
- GET—寻找特定id的数据输出,或获取资源(或整个集合)
或者,使用之前的/api/books示例:
“安全”和“幂等”的含义是什么?
“安全”的方法是不会改变数据的方法。REST建议GET只应该用来获取数据,因此它是上述集合中唯一的安全方法。不论你调用一个基于REST的GET方法多少次,它都不应该在数据库中改变任何东西。但这并不固有于动词——这取决于你如何实现它,所以你需要确保这一点。所有其他方法将以不同的方式改变数据,不能随机使用。在REST中,GET既是安全的也是幂等的。
一个“幂等”的方法是在多次使用中不会产生不同结果的方法。根据REST的说法,DELETE应该是幂等的——如果你一次删除一个资源,然后再次调用DELETE该资源,它不应改变任何东西。资源应该已经消失了。POST是REST规范中唯一的非幂等方法,所以你可以多次POST同一个资源,你会得到重复项。
让我们重新审视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中不嵌套作者,但我们可以发布链接,所以如果有人对作者感兴趣,他们可以去那里探索。
这不像成熟度模型的其他级别那样流行,但有些开发者使用它。例如Jira,下面是他们搜索API的一部分:
他们嵌套了你可以探索的其他资源的链接,以及这个问题的转换列表。他们的API很有趣,因为它在顶部有一个“扩展”参数。它允许你选择你不想要链接的字段,而是选择完整内容。
使用HATEOAS的另一个例子是Artsy。他们的API严重依赖HATEOAS。他们还使用JSONPlus调用规范,这为链接结构制定了特殊的约定。下面是使用HATEOAS进行分页的一个例子,这是使用HATEOAS的最酷的例子之一。
你可以提供指向下一个、上一个、第一个、最后一个页面的链接,以及你认为必要的其他页面的链接。这简化了API的使用,因为你不需要在客户端添加URL解析逻辑,或者添加分页号的方式。你只需得到已经结构化好的链接的客户端就可以使用了。
什么构成了一个好的API
到此为止Richardson的模型,但这并不是构成好API的全部。其他重要的质量是什么呢?
错误/异常处理
我期待从我使用的API中得到的一个基本的东西是,需要有一个明显的方式来告诉我是否有错误或异常。我需要知道我的请求是否已处理。
瞧,HTTP还有一种简单的方式来做到这一点:HTTP状态码。
控制状态代码的基本规则是:
- 2xx表示正常
- 3xx表示你要找的公主在另一个城堡——你要找的资源在另一个地方
- 4xx表示客户端做了一些错误的事情
- 5xx表示服务器失败
- 500内部服务器错误 - 小猫咪梗
至少,你的API应该提供4xx和5xx状态码。5xx有时是自动生成的。例如,客户端向服务器发送某些东西,它是一个无效请求,验证有缺陷,问题沿着代码下发,我们有一个异常——它将返回一个5xx状态码。
如果你想要致力于使用特定的状态码,你会发现自己在想,“哪个代码最适合这种情况?”这个问题并不总是容易回答。
我建议你去查阅RFC,它规定了这些状态码,比其他来源提供更广泛的解释,告诉你这些代码什么时候合适等等。幸运的是,有几个在线资源可以帮助你选择,比如Mozilla的HTTP状态码指南。
文档
伟大的API拥有伟大的文档。文档的最大问题通常是找人来更新它,随着API的增长。一个很好的选择是自我更新的文档,它与代码没有脱节。
例如,注释与代码无关。代码改变时,注释保持不变,变得过时。它们可能比没有注释还糟糕,因为过一段时间后它们将提供错误的信息。注释不会自动更新,所以开发者需要记得与代码一起维护它们。
自我更新文档工具解决了这个问题。一个流行的工具Apifox可以高效的帮助你解决问题。
可缓存性
在某些系统中,可缓存性可能不是大问题。你可能没有很多可以缓存的数据,一切都在不断变化,或者你可能没有很多流量。
但在大多数情况下,可缓存性对于良好的性能至关重要。它与RESTful API相关,因为HTTP协议与缓存有很多关系,例如HTTP头允许你控制缓存行为。
你可能希望在客户端缓存东西,或者在你的应用程序中缓存,如果你有一个注册表或值存储来保存数据。但HTTP允许你几乎免费获得良好的缓存,所以如果可能的话——不要错过免费的午餐。
此外,由于缓存是HTTP规范的一部分,很多参与HTTP的东西都会知道如何缓存东西:浏览器,它们天生支持缓存,以及你和客户端之间的其他中间服务器。
进化的 API 设计
构建API和现代软件的最重要部分是适应性。没有适应性,开发时间会减慢,尤其是在面对截止日期时,推出功能变得更加困难。
“软件架构”在不同的上下文中意味着不同的东西,但就目前而言,让我们采纳这个定义:
软件架构:避开阻碍未来变更的决策的行为/艺术。
考虑到这一点,当你设计你的软件并必须在具有相似好处的选项之间选择时,你应始终选择更具未来性的那一个。
好的实践并不是一切。以正确的方式构建错误的东西并不是你想要的。更好的是采纳成长的心态并接受变化是不可避免的,尤其是如果你的项目将继续增长的话。
为了让您的API更具适应性,其中一个关键做法是保持API层的轻便。真正的复杂性应该下放。
API 不应该决定实现
一旦你发布一个公共API,它就是固定的,你不能更改它。但如果你别无选择,只能承诺一个设计得不够好的API怎么办?
你应该始终寻找简化实现的方法。有时,用一个特殊的HTTP头来控制你的API的响应格式可能是一个比构建另一个API并称之为v2更简洁的解决方案。
API只是另一层抽象。它们不应该决定实现。有几种开发模式可以帮助你避免这个问题。
API 网关
这是一种外观模式开发模式。如果你将一个单体分解成一堆微服务,并想向世界公开一些功能,你只需建立一个API网关,它就像一个外观一样。
它将为不同的微服务(可能具有不同的API,使用不同的错误格式等)提供一个统一的接口。
针对前端的后端
如果你需要构建一个API来满足几种不同的客户端,这可能会很困难。为一个客户做出的决策会影响其他客户的功能。
针对前端的后端说——如果你有不同的客户喜欢不同的API,比如移动应用喜欢GraphQL,那就为他们建立API。
这只有在你的API是一个抽象层,并且很薄的情况下才有效。如果它与你的数据库耦合,或者太大,逻辑太多,你就无法做到这一点。
GraphQL 与 RESTful
GraphQL有很多炒作。它是新来的,但已经吸引了许多粉丝。以至于一些开发者声称它将取代REST。
尽管GraphQL相对于RESTful规范来说较新,但它们有很多相似之处。GraphQL的最大缺点是可缓存性——它必须在客户端或应用程序中实现。有客户端库具备内建的缓存能力(如Apollo),但这比利用HTTP提供的几乎免费的缓存能力更难。
技术上讲,GraphQL处于Richardson模型的0级,但它具有良好API的特性。你可能无法使用几项HTTP功能,但GraphQL旨在解决特定问题。
GraphQL在合并不同API并将它们作为一个GraphQL API公开时表现出色。
GraphQL在处理欠抓取和过度抓取方面表现出色,这是REST API可能难以管理的问题。这两者都与性能相关——如果你欠抓取,你没有有效地使用API调用,所以你必须进行很多调用。当你过度抓取时,你的调用导致的数据传输比必要的更大,这是带宽浪费。
REST与GraphQL之间的比较是一个很好的过渡,总结了一个好API的最重要特征。
- 详细了解:GraphQL vs RESTful API:如何选择?
好的API特性
- 你需要清晰表示数据——RESTful通过资源的形式为你提供这一点。
- 你需要展示哪些操作可用——RESTful通过结合资源与HTTP动词做到这一点。
- 需要有一种确认是否存在错误/异常的方法——HTTP状态码可以做到这一点,可能还有解释它们的响应。
- 有可发现性和导航的可能性很好——在RESTful中,HATEOAS负责这一点。
- 拥有出色的文档很重要——在这种情况下,可执行的、自更新的文档可以处理这个问题,这超出了RESTful规范的范畴。
- 最后但同样重要的是——伟大的API应该具备可缓存性,除非你的特定情况表明这不是必需的。
REST与GraphQL之间最大的区别是它们处理缓存的方式。当你按照REST方式构建你的API时,你几乎可以免费获得HTTP缓存。如果你选择GraphQL,你需要担心在客户端或你的应用程序中添加缓存。
- 源于:Software Development Services & Solutions Company – STX Next