跳至内容
免费开始
返回博客

REST API 架构约束:设计指南

Sorin-Gabriel Marica最后更新于 2 min read
REST API 架构约束:设计指南

开始提取

免费试用 WebScrapingAPI

领取 1,000 个免费 API 积分。无需信用卡。

开始使用
简而言之:REST 是一种通过对组件间交互施加约束来定义的架构风格。利用其六项约束来审查客户端独立性、请求上下文、缓存、统一接口、分层以及可选的下载代码;仅凭 JSON 响应本身无法证明其中任何一项。

一个以 JSON 格式返回订单的端点,仍可能迫使每个客户端理解服务器的私有工作流。REST API 架构约束是一套规则,用于判断这些交互是否遵循“表征状态转移”(Representational State Transfer),而非仅仅使用 HTTP。这些约束涉及参与方必须了解彼此哪些信息、如何交换表征,以及它们能够以多大程度独立地进行变更。

以读取产品目录的聚合器为例。它需要检索记录、发现下一页、识别未发生变化的结果,并在工作者失败后恢复处理。这些需求立即揭示了架构选择。下一次请求是否包含自身上下文?服务器能否提示下一页?已保存的响应是否可复用?接替的工作者在继续处理前是否必须恢复先前不可见的对话?

本指南将这些问题作为设计审查的依据。 示例描述的是一项假设性的目录服务,而非某提供商的可执行 API。它们展示了如何在不将 REST 简化为多个端点名称或 HTTP 动词列表的情况下,对行为进行审查。您可以将相同的审查方法应用于您发布的 API、您维护的集成,或是调用他人服务的数据采集管道。

将 REST API 架构约束视为设计测试

这六项约束包括客户端-服务器分离、无状态交互、可缓存性、统一接口、分层系统以及可选的按需代码。前五项定义了必需的特征;下载的可执行行为则是可选的。Roy Fielding 在其博士论文第 5 章中提出了这套约束。

请利用下表,将这些名称转化为你在审查过程中可以提出的具体依据。这些示例是设计问题,而非符合性测试套件,也不意味着仅凭一个标头就能使整个系统符合 REST 规范。

约束

需审查的证据

假设性目录问题

客户端-服务器

表示层与服务层职责之间的边界

命令行收集器能否在不依赖网站用户界面的情况下使用目录?

无状态

每次交互的完整上下文

另一个工作线程能否独立发出下一个请求?

可缓存

响应重用的定义条件

已保存的产品响应是否仍可用于此请求?

统一接口

资源标识、表示形式、消息语义和超媒体

响应是否解释了其数据及可用的转换?

分层系统

通过中间件工作的接口

添加网关是否要求客户端了解内部拓扑结构?

按需代码,可选

显式支持下载后执行

执行提供的脚本是否确实必要?

REST API 架构约束描述了一套交互机制。仅凭一个响应的截图无法全面体现所有约束。请收集一个包含读取、状态转换、错误及重复请求的简短跟踪记录,然后分析连接这些事件的假设。

同时应将架构与技术选型区分开来。HTTP 是资源导向型接口的天然环境,但架构风格并非网络传输格式的规范。如果您的团队仍在比较不同的设计体系,请参阅《REST API 与 SOAP API 对比指南》,其中解释了为何架构风格和消息协议需要不同的评估标准。

将客户端职责与请求上下文分离

假设一个商品目录服务发布产品描述和库存情况。浏览器负责选择视觉布局,而数据收集器则负责选择存储格式。这两种消费者都不需要知道该服务是读取单个数据库,还是整合了多个系统。这种分离是客户端-服务器设计的一个有用的起点。

请不要将其解读为必须为每个资源创建一个新的微服务。一个应用程序可以暴露一个清晰的边界。反之,如果调用者必须了解内部数据库名称、部署顺序或未记录的工作流阶段,那么许多微服务反而会暴露一个深度耦合的接口。

接下来的讨论涉及会话状态。假设某项服务要求客户端先选择一个仓库,然后根据该已记住的选择读取一个未加修饰的产品请求。替换的处理程序仅凭查看请求本身无法理解该请求。在无状态设计中,请求会明确标识相关仓库,或者携带足够的信息来解释所请求的操作。

这并不意味着服务会在每次响应后忘记产品、订单或权限。持久化的业务数据属于资源状态。问题在于理解后续请求所需的隐藏客户端交互上下文。当你在设计文档中明确区分这两种状态时,REST API 架构的约束就更容易应用。

身份验证也需要同样的谨慎对待。承载令牌(bearer token)并不能证明每次交互都是无状态的,而授权过程中的数据库查询也并不自动证明相反的情况。需检查是否无需重建先前的客户端交互即可理解该请求。然后单独检查授权:服务必须决定该调用者是否可以访问特定的仓库和产品。

对于数据采集器,需明确记录哪些请求输入属于某个账户、位置或采集任务。在将工作项在不同工作者之间传递时,应保持这些输入与工作项关联。如果集成还依赖于浏览器会话,在假设复制 URL 就能重现响应之前,请先了解 HTTP Cookie 是如何保存会话上下文的。

将缓存行为明确为数据契约

假设产品描述很少变化,而库存情况则会随时间波动。将两类响应都分配相同的长效重用窗口,虽然对基础设施管理很方便,但对消费者而言可能是不正确的。缓存设计应以信息的可接受有效期为起点,而非出于减少请求量的考虑。

对于 HTTP API,RFC 9111 定义了缓存复用及相关指令。响应可以禁止存储、要求复用前进行验证,或在明确条件下允许复用。 no-cache 与 no-store:前者允许存储,但要求在复用前验证通过;后者则指示缓存不要存储该响应。

实体标签为消费者提供了一个用于验证所选表示形式的验证器。收集器随后可以发送 If-None-Match;当 GET 请求满足相应条件时, 304 Not Modified 响应将允许其复用已保存的表示形式。该响应中没有需要解析的新表示体。若将其视为空目录,将导致原本正常运行的收集任务出现故障。

REST API 架构的约束要求定义明确的复用行为,而非通用的缓存机制。针对特定账户的报告可能需要严格的处理,而公共类别的描述则可容忍更长的有效期。设计审查应识别这两种情况,而非要求每个端点都必须命中缓存。

保持变体之间的区分。如果语言、凭据、位置或其他输入会影响响应,则需确定该输入如何参与缓存选择和策略。不要假设仅基于 URL 的缓存键就足以处理个性化数据。共享缓存对经过身份验证的请求也有特定的限制,因此应验证部署后的行为,而不是依赖于单个指令的存在。

在集合存储中,请将检索时间与源提供的修改时间分开保存。它们回答的是不同的问题:您何时观察到该表示,以及发布者声称其何时发生变更。这是为了可审计性而提出的实现建议,并非第七条 REST 约束。

审查统一接口的全部四个部分

统一接口是 REST API 架构约束中最容易被定义不足的部分。它包含四项要求:识别资源、通过表示形式操作资源、使消息自描述,以及使用超媒体来驱动应用程序状态。请对每一项进行评估,而不是仅将“使用 GET 和 POST”视为审查的全部内容。

识别资源并交换表征

产品标识符指代概念上的产品资源;其 JSON 文档是该资源在特定时间点的一种表示形式。渲染后的页面、图像或 XML 文档也可以是表示形式。围绕这一区别进行设计,有助于避免将数据库行布局暴露为永久性的公共契约。

对于假设中的产品目录,需明确当产品名称变更时哪个标识符保持不变。决定变体是否拥有独立身份,以及停产产品是否仍可被定位。这些属于领域层面的决策。实际检验的标准是:客户端能否在不猜测服务器存储方式的情况下,准确引用目标对象。

通过表示形式进行操作意味着客户端通过已发布的接口进行交互,而非直接修改私有服务器结构。提交的表示形式仍需经过服务器验证。客户端提供的价格、所有者标识符或权限字段,并不会仅仅因为请求使用了合适的媒体类型就成为权威信息。

让消息说明如何解释它们

自描述不仅需要可读的字段名称。方法语义、响应状态、媒体类型和相关元数据会告诉接收方如何处理该消息。相同的 JSON 文本,作为成功表示返回时与包含在错误响应中时,其含义可能截然不同。

使用 HTTP 时,应遵循其实际的方法语义。GET 是一种安全的检索方法;PUT 和 DELETE 在其定义的预期效果上具有幂等性。 幂等性并不要求每次重复操作都返回完全相同的状态,也不禁止日志记录。这些区别源自《HTTP语义》(RFC 9110),当集成系统考虑重试中断的操作时,这些区别至关重要。

收集器应在解析预期记录之前检查状态和媒体类型。网络连接成功但随后出现 HTML 网关错误的情况,并不构成有效的 JSON 目录。同样,语法上有效的 JSON 对象若包含错误模式,也不应被视为不包含任何产品的页面。

让表示形式暴露可用的过渡路径

“超媒体作为应用程序状态引擎”(常简称为 HATEOAS)意味着客户端从交互中提供的控件中选择可用的转换路径。一个目录表示体可能会暴露一个“下一页”链接,其关系具有明确的定义含义。收集器应遵循该控件,而不是基于对偏移量的私有假设来计算每个未来的 URL。

菲尔丁(Fielding)对超文本驱动 API 的阐释表明,这并非可选的修饰,而是一项核心要求。客户端仍需理解媒体类型和关系语义。在缺乏契约的情况下,超媒体并不能使每个陌生的业务工作流都变得易于理解。

为了进行实际验证,请将假设中的“下一页”URL 从偏移量改为不透明光标。 遵循所提供控制机制的客户端不会受到影响。而忽略该机制并自行递增数字的客户端,则会与某种构造规则产生耦合。RFC 8288 定义了 Web 链接和关系类型,为链接提供了比任意 URL 字段更精确的语义词汇。

因此,REST API 架构的约束提出了一个比“链接是否存在”更深层次的问题:受支持的转换是否真的能引导客户端?添加一个 self 链接并不能证明实现了完全统一的接口。

在各层和下载的代码中保持语义一致

一个收集器可能会到达一个网关,该网关将请求路由到缓存,然后转至应用程序。它应使用已发布的接口,而不依赖于哪个应用程序实例接收请求。在内部,运维人员可以在保持该边界不变的情况下更改路由或替换服务。

各层也是假设可能失效的地方。代理可能剥离请求头,网关可能替换为错误页面,或者缓存可能合并本应保持独立的响应。请审查端到端行为以及每个组件的配置。REST API 架构约束并不能保证中间件配置正确。

针对假设的目录,使用一个简短的跟踪矩阵:应用程序直接响应、网关响应、缓存重新验证以及上游超时。比较可观察的状态、表示元数据和错误契约。保留一个关联标识符以帮助运维人员追踪故障,但应避免要求客户端了解内部主机名或部署拓扑。

按需代码(Code-on-demand)解决的是另一个问题:服务器可以通过提供可执行代码来扩展客户端的行为。浏览器加载脚本就说明了这一机制。其可选状态很重要,因为许多 API 消费者有意只接受数据。服务器无需向无头收集器发送 JavaScript 即可满足强制性约束。

对于数据采集工作,应明确执行边界。如果页面需要通过浏览器执行来揭示数据,那只是目标系统的操作要求,并不意味着每个 REST 客户端都必须执行任意响应。同样,接收 HTML 内容并不等同于正在使用按需代码;HTML 内容与可执行行为是不同的关注点。

应将下载后的执行视为一项独立功能,并配备相应的控制措施。将 API 凭据置于与页面执行无关的上下文之外,并决定基于浏览器的收集器被允许访问哪些主机和导航目标。这些是围绕该示例的实际实现注意事项,而非对架构定义的新增内容。

将这些约束转化为集成审查

从真实的用户旅程入手,而非端点清单。以产品目录示例为例:检索一个类别,跟随页面跳转,重新访问未更改的产品,遇到不可用的资源,并在工作进程重启后恢复运行。捕获每个步骤中经过清理的请求和响应。

对于每项观察结果,都要区分已验证的属性与假设。工作进程重启成功一次表明上下文具有可移植性,但并不能证明其他地方不存在隐藏的会话依赖关系。下一页控件展示了一个公开的跳转,但不能证明整个应用程序都是由超媒体驱动的。

REST API 架构约束最适合作为评审词汇表使用。应记录“需要未文档化的前期仓库选择”,而非“不符合 REST 规范”;应记录“尽管提供了关系字段,客户端仍构建分页 URL”,而非“不现代”。具体的发现能指明开发人员可实施的更改以及评审人员可验证的内容。

然后根据后果对缺陷进行优先级排序。授权错误或跨账户缓存问题,其处理优先级应高于命名偏好。导致工作重复的重试机制,应制定有据可查的恢复策略。缺少可选的按需代码生成功能,无需仅为获得“REST”标签而进行整改。

对于外部服务,您可能无法更改其接口。请将收集器适配到已文档化的契约,保留必要的上下文,并如实描述其局限性。使用一个虽不符合严格 REST 标准但实用的 HTTP API,仍可能是一个合理的工程选择。真正的错误在于假设了接口从未承诺过的特性。

关键要点

  • 审查 REST API 架构约束时,应基于请求追踪和客户端依赖关系,而非仅看是否使用了 JSON 或是否采用了吸引人的端点名称。
  • 将持久化资源数据与隐藏的会话状态分离,并测试工作流是否能在消费者之间传递,而无需重建先前的交互。
  • 在启用激进的缓存或重试机制之前,应将数据新鲜度、有效性验证、表示变体和错误处理视为数据契约的组成部分。
  • 检查所有四项统一接口要求,包括有意义的过渡;仅列出 HTTP 方法仅涵盖设计的一部分。
  • 记录具体的集成限制及其后果,确保修复措施能解决可靠性和独立性问题,而不仅仅是术语上的问题。

常见问题

REST 是否规定在 URL 中包含 API 版本号?

不。相关约束并未规定版本号的通用位置。版本策略是一项契约决策,应阐明客户端如何发现变更以及如何保持兼容性。在采用任何命名约定之前,需明确哪些变更会实际导致现有用户无法正常使用,以及您将如何沟通或分阶段实施这些变更。

数据库事务与 REST 不兼容吗?

不。服务可以在内部使用数据库事务,同时对外暴露符合REST规范的接口。客户端应依赖于已发布的行为,而非数据库的实现。需明确操作所承诺的一致性级别、完成响应的含义,以及调用方在连接失败后如何调查不确定结果。

REST API 架构约束是否规定了速率限制?

不。它们不设定请求配额、并发限制或通用的等待间隔。这些属于服务的运营契约。消费者应阅读提供商文档中记载的限制,控制并发量,并保留足够的诊断信息,以便区分限流、验证错误或基础设施不可用等情况。

在确定标签前先审查行为

最高效的 REST 审查应以可观察的发现为终点。您应了解请求携带的上下文、响应何时可被复用、客户端如何解读响应,以及下一项受支持的操作源自何处。您还应了解哪些假设在网关背后或交互失败后仍未经过测试。

利用这些发现来改进下一次集成。将缺失的输入纳入显式契约、修正缓存边界,或指导客户端遵循给定的过渡规则。保持架构要求与运维建议之间的区分清晰可见,以便团队能够做出有意识的权衡。

如果您的数据管道还需获取仅通过网页可用的信息,请将 WebScrapingAPI 的 Scraper API 作为独立的采集输入进行评估。对该边界也应采用同样的规范:验证响应、保留采集上下文,并记录下游消费者对返回数据可能做出的假设。

关于作者

Sorin-Gabriel Marica, 全栈开发工程师 @ WebScrapingAPI

Sorin-Gabriel Marica

全栈开发工程师

索林·马里卡(Sorin Marica)是 WebScrapingAPI 的全栈及 DevOps 工程师,负责开发产品功能并维护确保平台平稳运行的基础设施。

开始构建

准备好扩展您的数据收集规模了吗?

加入2,000多家企业,使用WebScrapingAPI在无需任何基础设施开销的情况下,以企业级规模提取网络数据。