Day34-10|15.4.4 REST 规范:以资源和统一接口构建轻量服务

一、教材定位与学习目标

15.4.4“REST 规范”介绍面向 Web 和互联网 API 的一种重要服务设计风格。REST(Representational State Transfer)以资源为核心,通过统一接口、资源表示、无状态通信、可缓存和分层系统等约束,使客户端能够以较简单的方式访问和操作网络资源。

高级系统架构设计师考试不会只考 HTTP 方法的含义,而会考 REST 如何与 SOA、微服务和云原生结合:资源应该如何建模?GET、POST、PUT、PATCH、DELETE 如何使用?接口如何保证幂等?如何处理分页、过滤、错误和版本?REST 与 SOAP 什么时候各自更合适?本篇以企业 API 和订单服务为例展开。

二、REST 的基本思想

REST 不是一个必须安装的产品,也不是“用 HTTP 返回 JSON”这么简单。它是一组约束,主要包括客户端—服务器分离、无状态、可缓存、统一接口、分层系统和按需代码等。实际企业 API 不一定完全满足所有约束,但应理解这些约束带来的架构收益。

REST 的核心抽象是资源。用户、商品、订单、文件和支付单都可以被建模为资源,每个资源有唯一标识,客户端通过资源表示与服务交互。

客户端 → 统一接口 → 资源服务
             ↓
      资源表示、状态码和链接

资源与数据库表并不等同。订单资源包含业务状态和允许操作,不能简单把订单表的全部字段暴露为接口;一个业务动作也可能通过资源状态变化或专门动作接口表达。

三、REST 的主要约束

3.1 客户端—服务器分离

客户端负责用户体验和交互,服务器负责资源、业务规则和数据。双方通过契约协作,可以独立演进。移动端、Web 和第三方渠道可以使用同一业务资源,但表示和页面流程不必完全相同。

3.2 无状态

每个请求应包含完成处理所需的信息,服务器不依赖前一个请求保存在某个实例内存中的会话状态。状态可以保存在客户端令牌、数据库或共享缓存中,便于服务实例扩展、迁移和故障替换。

无状态不代表业务没有状态。订单的待支付和已支付状态仍然存在,只是它不依赖某台应用服务器的本地内存。认证令牌、幂等键和资源版本需要通过安全方式传递和管理。

3.3 可缓存

客户端、网关和 CDN 可以缓存明确允许缓存的响应,减少服务和数据库压力。缓存策略必须考虑资源版本、过期、权限和敏感数据,订单和个人信息不能因为配置错误被公共缓存。

3.4 统一接口

资源通过统一方法访问,响应使用一致的表示和状态码,降低客户端与服务之间的特殊耦合。统一接口包括资源标识、资源操作、消息自描述和超媒体等思想。

3.5 分层系统

客户端不需要知道请求经过 CDN、网关、缓存、代理和服务多少层。每层可以独立治理安全、缓存、路由和负载,但不能破坏端到端契约。

3.6 按需代码

服务可以向客户端返回可执行代码或动态能力,但企业 API 中使用较少。考试中了解其含义即可,重点仍在资源、统一接口、无状态、缓存和分层。

四、资源建模

4.1 资源命名

资源 URI 应稳定、可理解并体现层级关系。例如 /customers/{id}/orders 表示某客户的订单集合,/orders/{id} 表示单个订单。URI 通常描述资源,而不是把大量动词写在路径中。

4.2 资源与动作

查询订单使用 GET,创建订单可以使用 POST,替换资源使用 PUT,部分更新可使用 PATCH,删除或取消要根据业务语义设计。取消支付、确认收货等动作不一定是普通 DELETE,可以设计为状态变更或明确的动作资源,并定义权限、幂等和状态迁移。

4.3 聚合资源

客户端页面可能需要订单、商品、物流和会员信息。可以提供面向场景的聚合资源或 BFF,减少客户端多次调用,但聚合层不能重复实现核心规则,也不能让一个接口变成无限膨胀的万能查询。

4.4 资源表示

同一资源可以有 JSON、XML 或其他表示。表示应包含字段语义、版本、链接、时间、金额和状态。金额要明确精度和币种,时间要明确时区,敏感字段要脱敏。资源表示不应直接暴露内部数据库字段和实现细节。

五、HTTP 方法与幂等性

方法 常见语义 幂等性关注
GET 获取资源或集合 不应产生业务副作用
POST 创建资源或提交处理 通常非幂等,需业务幂等键
PUT 按指定 URI 创建或整体替换 设计为幂等
PATCH 部分更新 需明确重复执行语义
DELETE 删除或解除资源 通常设计为幂等,但业务需确认

HTTP 方法的标准语义不能代替业务幂等。POST 创建订单时,客户端重试可能产生重复订单,应使用请求幂等键;支付回调和退款也要以业务流水去重。即使使用 PUT,如果服务端每次调用都生成新的副作用,也不能称为真正幂等。

六、HTTP 状态码与错误模型

状态码帮助客户端区分请求结果,但业务错误不能全部塞进 200。常见类别包括:

类别 含义 例子
2xx 请求被成功处理或受理 查询成功、创建成功、异步受理
3xx 重定向或缓存相关 资源位置变化
4xx 客户端请求或权限问题 参数错误、未认证、无权限、资源不存在
5xx 服务端或依赖故障 内部错误、网关错误、依赖超时

错误响应应包含统一错误码、消息、关联请求号、可重试标识和必要细节。不要把堆栈和内部 SQL 返回给客户端。客户端根据错误类型决定修正参数、重新认证、有限重试、提示处理中或联系人工。

异步创建可以返回“已受理”以及任务或资源地址,客户端通过查询获得最终状态。接受请求不等于业务最终成功,尤其是支付、订单、文件处理和长流程。

七、分页、过滤与并发控制

集合查询需要支持分页、排序、过滤和字段选择,避免一次返回无限数据。分页方式可以是页码或游标,数据量大、实时变化频繁时游标通常更稳定。接口要限制最大页大小,防止恶意或错误请求消耗大量资源。

并发更新时,可以使用资源版本、ETag 或 If-Match 等机制检测客户端是否基于旧数据修改,避免后写覆盖先写。对订单、库存和账户等资源,版本控制要与业务状态机和权限结合,不能只靠 HTTP 头解决所有一致性问题。

八、缓存与条件请求

静态内容和公开商品信息适合 CDN 或网关缓存;个人订单、支付状态和敏感数据要限制缓存或按用户隔离。响应应根据 Cache-Control、过期时间和资源版本制定策略。

条件请求可以让客户端携带资源版本或修改时间,服务器在资源未变化时返回未修改结果,减少传输。缓存失效和并发更新仍需业务设计,价格和库存等动态数据在交易时要重新确认。

九、版本与兼容治理

REST API 版本可以通过 URI、请求头、媒体类型或网关路由表达。版本策略应尽量保持向后兼容:新增可选字段和新资源通常风险较低,删除字段、修改语义和改变必填规则需要迁移窗口。

服务端应统计消费者、版本和调用量,发布新版本时进行契约测试、灰度和回滚。数据库变更采用兼容迁移,消息和 API 不能因为内部字段变化而突然失效。版本不是越多越好,长期维护多个版本会增加安全和运维成本。

十、REST 安全设计

REST 接口应使用安全传输、身份认证、授权、输入校验、限流和审计。令牌应有有效期、受众和权限范围,不能把敏感信息直接放在 URL;权限要按用户、租户、组织、资源和动作控制。

防重放、幂等和 CSRF 防护根据认证方式和业务场景设计;文件上传限制类型、大小和存储路径;错误和日志脱敏;网关可提供 WAF、认证、限流和审计,但业务服务仍需做业务授权和数据校验。

十一、REST 与 SOAP 的选型

REST 适合 Web、移动端、公开 API 和轻量服务,使用 HTTP 生态、缓存和代理较方便;SOAP 适合强契约、跨组织、消息级安全、可靠消息和复杂企业扩展。REST 不是天然安全或可靠,SOAP 也不是天然高可用,二者都需要认证、授权、幂等、超时和治理。

选型应考虑:消费者类型、协议生态、消息复杂度、性能、跨组织互操作、可靠性、安全、事务、版本和团队能力。企业可以对外使用 REST,对内部遗留系统通过 SOAP 或适配器集成,不必全系统使用一种风格。

十二、案例分析:零售订单 REST API

某零售平台为商城、门店和第三方渠道提供订单 API。要求支持查询、创建、取消、支付状态和物流查询,第三方可能重试请求,订单数据不能越权访问。

可以将订单建模为 /orders/{id},集合查询支持分页和游标;创建使用 POST 并强制请求幂等键,服务端保存幂等结果;取消不简单删除订单,而是通过明确的状态迁移接口或动作资源,根据当前状态判断是否允许;支付状态通过查询和异步回调更新,不能只依赖客户端;响应使用统一状态码、业务错误码和请求标识;订单查询按用户、门店和渠道权限过滤;敏感字段脱敏,接口使用 HTTPS、令牌和审计;商品和物流查询可以缓存,订单和支付状态限制缓存;版本通过 URI 或媒体类型管理,使用契约测试和灰度发布。

服务内部仍使用本地事务、状态机和数据库约束,支付和物流跨系统使用事件、幂等、补偿和对账。REST 只规定接口风格,不代替业务一致性设计。

十三、课后练习

练习一:选择题

下列哪项最符合 REST 资源设计思想?

A. /doCreateOrderNow

B. /orders/{id} 并通过统一方法和状态表达订单操作

C. /database/table/order

D. /executeSql

答案:B。 REST 以资源为核心,避免把数据库结构和内部动词直接暴露为公共接口。

练习二:简答题

为什么 POST 创建订单通常需要额外的幂等键?

参考答案: POST 常用于创建资源,网络超时或响应丢失时客户端可能重试;如果服务器无法识别同一业务请求,可能重复创建订单。幂等键与用户、商品或业务单关联,服务端保存处理结果和状态,重复请求返回原结果或当前状态。支付、库存和消息处理还要分别使用业务流水和唯一约束。

练习三:案例题

某 REST API 所有请求都返回 HTTP 200,业务错误写在 success=false 字段中;客户端无法判断哪些错误可重试。请分析改进。

参考答案: 统一返回 200 混淆了传输成功、业务受理和处理失败,网关、监控和客户端无法按错误类别治理。应使用适当 HTTP 状态码区分参数、认证、授权、资源不存在、冲突、限流和服务端故障;响应体统一包含业务错误码、消息、请求标识、重试建议和必要细节;业务拒绝与技术失败分开,客户端根据错误类型决定修正、重新认证、有限重试或等待查询。写操作必须幂等,异步操作返回受理状态和资源查询地址;网关和监控按状态码、错误码和业务指标告警,日志脱敏并保留审计。

十四、论文写作素材

本节适合写“论 REST API 设计与治理”“论面向互联网的服务架构”或“论企业 API 的安全与可靠性”。论文可围绕资源建模、统一接口、无状态、缓存、分页、版本、幂等、错误和安全展开。

写作时要避免把 REST 简化成“HTTP+JSON”。应说明资源不是数据库表,状态码和错误模型要支持治理,缓存不能泄露敏感数据,POST 需要业务幂等,异步受理不等于最终成功,接口版本要兼容和灰度。最后以延迟、错误率、重复率、缓存命中率和接口可用性验证。

十五、今日复盘清单

  • 我能否解释 REST 的主要架构约束?
  • 我能否把用户、订单和商品建模为资源?
  • 我能否正确使用 HTTP 方法、状态码和错误模型?
  • 我能否说明无状态、缓存、分页、并发控制和版本治理?
  • 我能否为 POST、支付和异步回调设计幂等?
  • 我能否比较 REST 与 SOAP 的适用场景?

本节的核心结论是:REST 通过资源、统一接口、无状态、可缓存和分层等约束,提供适合 Web 和互联网服务的轻量协作方式。良好的 REST API 不只是返回 JSON,而是要同时设计资源边界、状态语义、幂等、错误、版本、缓存、安全和业务一致性。