RESTful API 设计的关键技巧

简介: REST,代表表现层状态转移(Representational State Transfer),长久以来一直是 API 服务的圣杯,最初由 Roy Fielding 在其博士论文中定义。尽管它不是构建 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。他们还使用 JSON Plus 调用规范,这为链接结构制定了特殊的约定。下面是使用 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 的最重要特征。

好的API特性

  • 你需要清晰表示数据——RESTful 通过资源的形式为你提供这一点。
  • 你需要展示哪些操作可用——RESTful 通过结合资源与 HTTP 动词做到这一点。
  • 需要有一种确认是否存在错误/异常的方法——HTTP 状态码可以做到这一点,可能还有解释它们的响应。
  • 有可发现性和导航的可能性很好——在 RESTful 中,HATEOAS 负责这一点。
  • 拥有出色的文档很重要——在这种情况下,可执行的、自更新的文档可以处理这个问题,这超出了 RESTful 规范的范畴。
  • 最后但同样重要的是——伟大的 API 应该具备可缓存性,除非你的特定情况表明这不是必需的。

REST 与 GraphQL 之间最大的区别是它们处理缓存的方式。当你按照 REST 方式构建你的 API 时,你几乎可以免费获得 HTTP 缓存。如果你选择 GraphQL,你需要担心在客户端或你的应用程序中添加缓存。

相关文章
|
8天前
|
SQL 缓存 测试技术
构建高性能RESTful API:最佳实践与避坑指南###
—— 本文深入探讨了构建高性能RESTful API的关键技术要点,从设计原则、状态码使用、版本控制到安全性考虑,旨在为开发者提供一套全面的最佳实践框架。通过避免常见的设计陷阱,本文将指导你如何优化API性能,提升用户体验,确保系统的稳定性和可扩展性。 ###
44 12
|
4天前
|
JSON JavaScript API
深入浅出Node.js:从零开始构建RESTful API
【10月更文挑战第39天】 在数字化时代的浪潮中,API(应用程序编程接口)已成为连接不同软件应用的桥梁。本文将带领读者从零基础出发,逐步深入Node.js的世界,最终实现一个功能完备的RESTful API。通过实践,我们将探索如何利用Node.js的异步特性和强大的生态系统来构建高效、可扩展的服务。准备好迎接代码和概念的碰撞,一起解锁后端开发的新篇章。
|
7天前
|
存储 API 开发者
深入理解RESTful API设计原则
本文探讨了RESTful API的设计原则,强调了其在现代Web服务中的重要性。通过分析状态表示转移(REST)的概念、核心约束以及最佳实践,本文旨在为开发者提供构建高效、可扩展和易于维护的API的指导。文章还讨论了常见的设计陷阱和如何避免它们,以确保API设计的健壮性和灵活性。
|
9天前
|
JSON 缓存 API
构建高效RESTful API的最佳实践
【10月更文挑战第34天】在数字时代的浪潮中,后端开发扮演着至关重要的角色。本文将带你深入探索如何构建高效的RESTful API,从设计原则到实际编码技巧,再到性能优化和错误处理,我们将一一解锁这些技能。你将学会如何打造一个既优雅又强大的后端服务,让你的应用程序在激烈的市场竞争中脱颖而出。那么,让我们一起踏上这段精彩的旅程吧!
24 2
|
10天前
|
XML JSON API
【PHP开发专栏】PHP RESTful API设计与开发
随着互联网技术的发展,前后端分离成为Web开发的主流模式。本文介绍RESTful API的基本概念、设计原则及在PHP中的实现方法。RESTful API是一种轻量级、无状态的接口设计风格,通过HTTP方法(GET、POST、PUT、DELETE)操作资源,使用JSON或XML格式传输数据。在PHP中,通过定义路由、创建控制器、处理HTTP请求和响应等步骤实现RESTful API,并强调了安全性的重要性。
18 2
|
12天前
|
存储 安全 API
深入理解RESTful API设计原则
本文旨在探讨RESTful API设计的基本原则和最佳实践,帮助开发者构建高效、可维护的Web服务。通过分析REST架构的核心概念,如资源、统一接口、无状态通信等,本文将指导读者如何设计符合REST原则的API,以及如何处理常见的设计挑战,如版本控制、错误处理和安全性问题。
|
14天前
|
存储 缓存 API
深入理解RESTful API设计原则
【10月更文挑战第28天】 在现代软件开发中,RESTful API已经成为了前后端分离架构下不可或缺的一部分。本文将探讨RESTful API的核心设计原则,包括资源导向、无状态性、统一的接口以及可缓存性等关键概念,并通过实例解析如何在实际应用中遵循这些原则来设计高效、可扩展的API。我们将深入了解REST架构风格的理论基础,并讨论其对提升系统互操作性和简化客户端实现的重要性。
50 3
|
15天前
|
JavaScript 中间件 API
Node.js进阶:Koa框架下的RESTful API设计与实现
【10月更文挑战第28天】本文介绍了如何在Koa框架下设计与实现RESTful API。首先概述了Koa框架的特点,接着讲解了RESTful API的设计原则,包括无状态和统一接口。最后,通过一个简单的博客系统示例,详细展示了如何使用Koa和koa-router实现常见的CRUD操作,包括获取、创建、更新和删除文章。
35 4
|
17天前
|
XML API 网络架构
深入理解RESTful API设计原则与实践
【10月更文挑战第26天】在数字化浪潮中,API(应用程序编程接口)成为连接不同软件组件的桥梁。本文将深入浅出地探讨如何根据REST(Representational State Transfer)原则设计高效、易于维护和扩展的API,同时分享一些实用的代码示例,帮助开发者构建更加健壮和用户友好的服务。
|
8天前
|
JavaScript 前端开发 NoSQL
深入浅出:使用Node.js构建RESTful API
【10月更文挑战第35天】在数字时代的浪潮中,后端技术如同海洋中稳固的灯塔,为前端应用提供数据和逻辑支撑。本文旨在通过浅显易懂的方式,带领读者了解如何利用Node.js这一强大的后端平台,搭建一个高效、可靠的RESTful API。我们将从基础概念入手,逐步深入到代码实践,最终实现一个简单的API示例。这不仅是对技术的探索,也是对知识传递方式的一次创新尝试。让我们一起启航,探索Node.js的奥秘,解锁后端开发的无限可能。