REST 架构入门

欢迎来到现代 Web 服务的基础概念。以 REST 最为人所熟知的表示性状态转移,是一种定义用于创建 Web 服务的约束集的架构风格。由 Roy Fielding 在其博士论文中提出,REST 不是标准或协议,而是一种高级设计理念。它为计算机系统在互联网中的通信提供了标准化方式,确保不同应用能够无缝理解对方。遵循 REST 原则,开发者能够构建可扩展、具备弹性且易于长期维护的系统。

在这种架构风格的核心是一切皆资源的原则。资源可以是客户端可能希望交互的任何信息或数据实体,例如客户记录、博客文章或金融交易。REST 要求系统应以无状态方式进行通信,依赖网络的健壮、成熟的基础设施。这意味着利用标准的互联网协议,几乎专门使用超文本传输协议(HTTP)在请求数据的客户端与承载数据的服务器之间传输数据。

理解 REST 需要将思维从“执行动作”转变为“对事物”的思考。在较早的远程过程调用(RPC)体系中,系统通过告知彼此要执行哪些函数来进行通信。在 RESTful 架构中,系统通过传输资源当前状态的表示来进行通信。当客户端请求一个资源时,服务器会以表示该资源在特定时刻的有效载荷进行响应,格式通常为 JS 对象表示法(JSON)或可扩展标记语言(XML),等具有全球通用可理解 medium。

资源与 URI 的概念

由于 REST 围绕资源展开,准确且一致地标识这些资源至关重要。在 REST 架构中,每个资源都被分配一个统一资源标识符(Uniform Resource Identifier, URI),它是资源在网络上的唯一地址。该标识符必须具有逻辑性、层次性,且便于开发者理解。设计良好的地址结构使 API 的使用者能够预测在哪里可以找到相关数据,而无需记忆复杂的文档。

行业标准最佳实践是使用复数名词来命名这些资源。不要在地址中使用动词来描述动作,而应仅描述实体本身。例如,如果你正在构建一个管理员工记录的接口,基地址应简单地为 /employees。采用名词型的地址结构可保持地址简洁,并将重点放在数据主体而非正在执行的操作上。使用像 /get-employees 或 /create-employee 这样的动词明显违反 REST 设计原则。

当客户端需要与单个具体资源交互而非资源集合时,唯一标识符将被附加到地址路径中。如果客户端想访问数据库编号为 42 的员工记录,合适的地址将变为 /employees/42。这种层次结构可以扩展以表示不同类型资源之间的关系。如果你需要查看该员工的工资单记录,地址就会逻辑性地扩展为 /employees/42/payrolls。

REST 标准 HTTP 方法

在数据的地址由统一资源标识符提供时,HTTP 方法提供了要执行的操作。REST 利用超文本传输协议(HTTP)中内置的标准方法,指示服务器对目标资源执行何种操作。这种地址与操作的分离,是使 RESTful 界面如此可预测的原因。你每天最常用的四个方法是 GET、POST、PUT 和 DELETE。

GET 方法专为检索信息而设计。当客户端发出 GET 请求时,表示请求服务器返回资源的表示形式而不以任何方式修改它。该方法被定义为既安全又幂等。安全意味着它不会改变服务器的状态,幂等意味着对同一请求执行十次,其结果与执行一次时在服务器上的结果完全相同。你可以把 GET 请求简单地理解为读取文档。

POST 方法用于在服务器上创建全新的资源。当客户端向集合地址提交 POST 请求时,包含新实体数据的数据负载随之一起发送。与 GET 方法不同,POST 既不安全也不幂等。多次提交相同的 POST 请求会在服务器上创建多个不同的资源。服务器处理负载,生成新资源的唯一标识符,并将其存储在数据库中。

要修改现有资源,开发人员依赖 PUT 和 DELETE 方法。PUT 方法通过用请求负载中提供的新的状态来完全替换当前状态来更新资源。如果资源不存在,PUT 请求也可能创建它,尽管通常用于完整更新。DELETE 方法,顾名思义,请求服务器永久移除指定资源。PUT 和 DELETE 都旨在幂等,即对同一特定资源发送多次相同的更新或删除请求,应使系统保持与第一次请求相同的状态。

将操作映射到业务逻辑

在软件工程中,持久存储所需的基础操作是创建(Create)、读取(Read)、更新(Update)和删除(Delete)。REST 提供了这些数据库操作与我们刚才讨论的 HTTP 方法之间的直接映射。Create 与 POST 完全一致,Read 精确对应 GET,Update 对应 PUT,Delete 对应 DELETE 方法。这种标准化的映射意味着,一旦开发者理解了底层数据模型,他们就直观地知道如何与接口交互。

设想一个实际的商业场景,你正在为零售书店建立一个库存管理系统。该系统的核心资源是书籍。当出版社发布新小说时,库存管理员需要将其添加到系统中。客户端应用将组装书籍信息,如书名、作者和价格,并将它们在针对 /books 地址的 POST 请求中发送。服务器接收后创建记录,通常会以新分配的识别号码进行响应。

稍后,一名员工注意到某本书的价格输入错误。为修正,客户端应用对特定资源地址发出 PUT 请求,如 /books/892。该请求的负载包含经完全更正的书籍信息。服务器定位到该标识符的书籍,并用更正数据替换其现有数据。如果该书籍停印并需要从活动目录中彻底移除,客户端将向同一具体地址发送 DELETE 请求。

显示四个 CRUD 操作的左侧视觉映射图,运用箭头连接到右侧对应的 GET、POST、PUT 和 DELETE HTTP 方法,以及用于书店库存系统的示例 URI

无状态性与服务器响应

REST 的一个最关键约束是,所有客户端与服务器之间的交互必须完全无状态。这意味着服务器不得在各个请求之间存储关于客户端会话的任何信息。客户端发起的每一个请求都必须包含服务器理解并完成操作所需的全部上下文、认证凭证和数据。服务器将每个进入的请求视为一个完全独立的事务,而对之前的请求一无所知。

这种严格的无状态性正是 REST 架构获得巨大可伸缩性的原因。因为服务器不需要分配内存来跟踪用户会话,它释放了大量计算资源。此外,在大规模分布式系统中,负载均衡器之后的任意服务器都可以处理来自任意客户端的请求。如果某台服务器崩溃,另一台可以立即接替它的位置,而用户也不会丢失会话数据,因为会话数据完全保存在客户端。

为了传达这些独立事务的结果,服务器使用标准的HTTP状态码。这些三位数字会立即告知客户端操作是成功还是失败。两百范围的代码表示成功,例如200 OK或201 Created。四百范围的代码表示客户端错误,意味着请求格式错误或客户端请求了不存在的资源。最后,五百范围的代码表示服务器在尝试处理一个完全有效的客户端请求时遇到了意外错误。

REST设计中的常见错误

尽管广泛采用REST,开发者仍然会犯违反其核心原则的设计错误。最普遍的错误是通过在资源地址中加入动词来回避远程过程调用的习惯。像 /add-book 或 /update-user 这样的地址破坏了接口的一致性。HTTP方法已经描述了行动,因此在地址中再添加动词会造成冗余和混淆。地址应严格标识名词,而HTTP方法处理动词。

另一个严重错误是错误地使用GET方法来修改或删除数据。有时开发者会创建像 /users/5/delete 这样的地址,并指示客户端通过GET请求访问它。这非常危险,因为标准的网络基础设施会将GET请求视为安全操作。网络浏览器会预取它们,缓存服务器会对其进行重复使用,搜索引擎爬虫会自动对其进行索引。如果GET请求触发删除,简单的网页爬虫遍历你的网站就可能意外地清空整个数据库。

最后,不一致的复数形式会给使用接口的开发者带来摩擦。如果获取所有用户的地址用复数形式 /users,而获取单个账户的地址用单数形式 /account,使用者就必须经常查看文档来记住拼写。行业公认的标准是对所有集合和单独资源都仅使用复数名词。接口一致性是一个良好REST设计的标志,确保它在未来很长时间内仍然直观且易于集成。

课程检查点

1. REST在网络服务中的含义是什么?

2. 下列哪项是REST URI中命名资源的行业标准做法?

3. 哪种 HTTP 方法专门用于检索信息,并被视为既安全又幂等?

4. 客户端应用应如何请求服务器创建一个全新的资源?

5. 为何无状态性约束对 REST 架构中的服务器设计至关重要?

6. 使用 GET 方法删除资源会带来什么危险后果?

REST 原则与 HTTP 方法入门