这波讨论源于 V2EX 上的一个热议话题:有位开发者反馈,同事将其所有接口统一为 POST 请求,并坚称 HTTPS 协议下 POST 更加安全。这让习惯于 RESTful API 设计的该开发者感到困惑,并质疑既然如此,GET、PUT、DELETE 等动词存在的意义。该帖子引发了多种回应,一部分人认为统一使用 POST 能够提高开发效率,减少沟通成本,甚至有人认为“工作而已,优雅不能当饭吃”。然而,这种做法可能忽视了 API 设计中的诸多关键原则。
本文将从以下几个方面,对“REST API 全用 POST”的常见误区进行深入剖析:
**HTTP动词的深层意义**
编程世界通常包含业务逻辑和控制逻辑。业务逻辑指实现用户需求的功能代码,如数据保存、查询、订单处理等;控制逻辑则关注程序运行的非功能性代码,如循环控制、多线程、协议选择等。HTTP 协议同样拥有协议头和协议体,其中协议头承载控制逻辑,协议体则传递业务数据。HTTP 动词(Method)作为协议头的一部分,其主要职能就是体现控制逻辑。
根据 RFC7231 章节4.2.2 等规范,REST API 的开发者应严格遵循 HTTP 动词的语义:
* **GET**:用于查询操作,对应数据库的 select,具备幂等性。 * **PUT**:用于资源的整体更新,对应数据库的 update,具备幂等性。若资源不存在可创建。 * **DELETE**:用于资源的删除,具备幂等性。 * **POST**:用于新增操作,对应数据库的 insert,不具备幂等性。 * **HEAD**:获取资源元数据或探查 API 健康状况,具备幂等性。 * **PATCH**:用于资源的局部更新,不具备幂等性。 * **OPTIONS**:获取 API 相关信息,具备幂等性。
需要注意的是,PUT 和 PATCH 均用于更新,但 PUT 旨在更新资源的完整信息,而 PATCH 适用于部分字段的更新(参考 RFC 5789)。在实际应用中,如用户登录 API,虽然涉及数据库查询,但其核心语义是创建或更新用户会话状态,因此更适合用 POST。而注销则可使用 DELETE。因此,不应机械地将 HTTP 动词与数据库 CRUD 操作一一对应,而应关注业务语义。
**幂等性在API设计中的重要性**
API 的幂等性,即多次执行与单次执行效果相同且无副作用,对于控制逻辑至关重要。例如,交易订单的新增(POST)不幂等,而删除操作(DELETE)或整体更新(PUT)则具备幂等性。PATCH 虽然用于局部更新,但若涉及计数器增减等操作,则不幂等。
在远程调用中,网络超时是常见问题。若请求超时,我们无法确定服务端是否已执行。对于非幂等操作,贸然重试可能导致灾难性后果,如银行转账可能导致重复转账。而遵循 HTTP 动词规范,开发者可明确哪些操作可安全重试,哪些不可。若所有 API 均使用 POST,则这种控制能力将完全丧失。
除了幂等性,HTTP 动词的区分还支持更精细的控制逻辑,包括:
* **缓存**:GET 请求易于通过 CDN 或网关进行缓存。 * **流控**:可根据动词对读写操作进行不同粒度的频率限制。 * **路由**:将读请求路由至读服务,写请求路由至写服务。 * **权限**:实现更细致的权限控制与审计。 * **监控**:针对不同方法进行性能分析。 * **压测**:区分动词有助于更精准地进行压力测试。
即使业务再简单,至少也应实现“读写分离”,即 GET 用于读操作,POST 用于写操作。
**RESTful复杂查询的实践**
对于查询类 API,常见的四种操作是排序、过滤、搜索和分页。参考 Microsoft REST API Guidelines 和 Paypal API Design Guidelines,建议如下:
* **排序**:使用 `sort` 关键字,如 `GET /admin/companies?sort=rank|asc`。 * **过滤**:使用 `filter` 关键字和 `op` 操作符,如 `GET /companies?category=banking&location=china`。对于复杂表达式,可在 URL 中构造,并对特殊字符转义,如 `GET /products?$filter=name eq 'Milk' and price lt 2.55`。 * **搜索**:使用 `search` 关键字,如 `GET /books/search?description=algorithm`。 * **分页**:应作为默认行为。使用 `page` 和 `per_page`(如 `GET /books?page=3&per_page=20`),或使用绝对位置参数 `max_id`、`published_before` (如 `GET /news?max_id=23454345&per_page=20`)。
尽管理论上 GET 可以带请求体,但考虑到许多库和中间件不支持,对于复杂查询,例如 ElasticSearch 的 DSL,在客观不支持 GET 携带请求体的情况下才考虑 POST。ElasticSearch 官方文档也建议优先使用 GET,因为其语义更清晰。然而,需注意 ElasticSearch 7.11 后已不再支持 GET 携带请求体。
对于更复杂的业务操作,建议通过调用多个 API 来完成,以降低后端程序与数据耦合度,适应微服务架构。若需要像 GraphQL 那样的查询语言,可考虑采用 OData 这样的开放协议解决方案。
**对常见误区的回应**
1. **API为何要符合RESTful规范?** RESTful API 是 HTTP API 的行业共识和标准。遵循此规范,可无缝享受 CDN、API 网关、服务治理、监控等技术红利,大幅降低研发成本,避免潜在问题。 2. **“过早优化”不适用于API设计?** API 是一种契约,一旦投入使用,修改成本极高。接口设计如同数据库模式设计,应在早期进行慎重规划。参考 Microsoft、Paypal 或 Google 的 API 设计指南,是良好实践的基石。 3. **POST更安全吗?** 这种说法不准确。GET 请求数据在 URL 中,POST 在请求体中,但只要是 HTTPS,整个请求数据(包括 URL PATH)都会加密。认为 GET 不安全是因为其可能出现在日志或浏览器历史记录中,但 POST 在 CSRF 等安全问题上同样面临风险。安全是一个复杂议题,无论使用何种方法,均需认真对待,例如对敏感信息加密、对 URL 签名、使用 HMAC 等认证技术防止恶意链接欺诈。 4. **全用POST能节省时间、减少沟通?** 恰恰相反。为 API 赋予不同动词几乎不耗时,且符合优秀编程风格。规范化的 API 设计能显著减少新成员学习成本和跨团队沟通成本,因为标准是人类协作的基础。非标准的“一把梭”方式反而会增加使用者不断提问的频率,短期看似快速,长期则积累技术债,导致更高的维护成本。 5. **“早点回家”的正确姿势** 真正的“长期早回家”建立在高质量的代码和设计之上。良好的扩展性让代码在需求变更时改动小甚至无需改动;完善的文档和注释则减少了他人寻求帮助的频率,甚至让代码易于交接。这样的工作方式才能确保下班后不受打扰。 6. **“工作而已,优雅不能当饭吃”?** 将遵循规范称为“优雅”实则暴露了低标准。作为职业程序员,应热爱并尊重自己的职业,通过产出高质量、符合行业规范的标准来提升职业含金量,而非仅仅为了薪酬而生存。尊重自己的职业,才能赢得他人的尊重,成为真正的职业化程序员,而非仅仅是“码农”。
权力来自工作,尊重则源于行动。
