一个严重的误解是 REST
的 API
必须是基于 CRUD
的,这两者之间没有任何的联系,都只是API设计风格的一种方式而已。本文还将介绍基于 REST 的 API 的几种实现规则。
CRUD简介
基于 CRUD
的 API 是指提供包含实例的资源集合的 API,效仿 create、read、update 和 delete 生命周期模式。当有一组代表内容或状态的资源实例时,CRUD
模式很有用,它通常遵循以下模式:
GET /articles
– 列出/分页/过滤文章列表POST /articles
– 创建新文章GET /articles/{articleId}
– 获取文章详情PATCH /articles/{articleId}
– 更新文章详情DELETE /articles/{articleId}
– 删除指定ID的文章
REST:超越 CRUD
在讨论 CRUD 之外的API风格之前,需要澄清一个关于 REST 的严重误解:
CRUD 并不是对“RESTful”API 的要求。
事实上,在 Fielding’s dissertation 的论文中并没有提及 CRUD
。
在谈到基于 REST 的 API 时,很多人将利用基于 CRUD 风格的资源与 REST 风格混淆一起。但是,这些其实是 HTTP 抽象的资源概念,与数据库设计没有直接关系:
本规范没有限制可能是资源,相反“资源”一词是在一般意义上使用的用于任何可能被 URI 标识的内容。—— RFC 3986
REST 是规范客户端和服务端之间通讯的方式,利用 HTTP 协议提供的功能,这些限制可以自由地专注于 API 设计:
- 统一的接口:来自不同客户端的请求都是一样的,无论客户端是浏览器、移动设备还是其他任何设备
- 客户端-服务端分离 :客户端和服务端独立运行,它们之间的交互仅以请求和响应的形式进行
- 无状态 :服务端不存储使用 API 的用户的任何信息,因此客户端必须在每次请求时提供处理请求的所有必要信息
- 分层系统:客户端不知道在客户端和响应请求的实际服务器之间有多少层,这是 HTTP 的一个关键原则,允许缓存服务器、反向代理和访问安全分层——所有这些对发送请求的客户端都是透明的
- 可缓存 : 服务端响应必须包含有关数据是否可缓存的信息,允许客户端或代理在 API 服务器之外缓存数据
- 按需代码(可选):能够根据请求将可执行代码从服务器发送到客户端,从而扩展客户端功能。
同样,REST 不需要具有基于 CRUD
风格的资源。CRUD
是一种我们可以应用于 REST
的 API 风格,但它不是构成基于 REST API 的必要条件。
这在设计 API 时可以更加的自由,这样可以在适当的时候提供具有基于 CRUD 风格的资源,而在不需要CRUD的时候混合使用功能性资源。
在设计 API 时,不必局限于这种风格,可以探索一些其他扩展风格,可以帮助设计出满足当前和未来开发需求的出色的API风格。
使用端点扩展 CRUD
API 设计中的一个常见情况是需要超越典型的 CRUD 交互模型,例如,某些 API 可能需要支持提交、 批准、拒绝和发布等操作。由于在 HTTP 中的方法有限,如何将其添加到的 API 中?
一种选择是使用通过 PATCH
请求修改的状态字段,虽然这是一种选择,但觉得使用功能端点可能更加清晰,如下:
- PUT
/articles/{articleId}/submit
- PUT
/articles/{articleId}/approve
- PUT
/articles/{articleId}/decline
- PUT
/articles/{articleId}/publish
在此设计中,为集合中的文章实例提供了功能端点,通过 PUT
操作使用(意味着只是更新),这样设计有以下好处:
- 在 API 管理层可以有更好的细粒度访问管理,每个特定的操作都是一个唯一的 URL,可以分配不同的基于角色(RBAC)的访问。 因此,将访问权限从代码中解耦到部署模型中,可以灵活地进一步限制端点访问,而无需更改代码来强制执行这些限制
- API 支持的工作流更加明确,不必查看
PATCH
端点来查看可以更新的有效状态值,以及允许转换到的状态和时间的状态机规则。每个端点都独立且更直观地声明了这一点 - 最后,通过为额外的流程端点提供唯一的 URL ,能够在响应负载中更有效地利用超媒体链接。例如,作者可能会看到以下 API 响应:
超媒体链接:API不仅仅是HTTP上的数据存储,而是成为HTTP上的状态机。它仍然有数据,但是它也可以以自我描述的方式提供"下一个可用动作"
{ name: "布道api", status: "draft", _links: [{ rel: "submit", href: "/articles/99/submit" }], }
一旦文章草稿提交,编辑的时候可能会看到以下内容:
{ name: "api布道", status: "draft", _links: [ { rel: "approve", href: "/articles/99/approve" }, { rel: "decline", href: "/articles/99/decline" }, ], }
客户端能够根据用户的权限了解客户当前可以使用哪些操作(例如,编辑可能能够批准、拒绝和发布,而作者可能只能以草稿模式提交文章) 通过是否存在指向“下一个可用动作”的端点链接。
对于需要超越其提供的部分或全部资源的标准创建、读取、更新和删除操作的 API 设计来说,这是一个非常实用的选项。
用于搜索和计算的功能资源
如果需要为消费者提供的能力不需要资源集合怎么办?如果只需要计算一个值并返回它呢?这会破坏基于 REST 的方法吗?不需要,只要我们遵守 HTTP 规则并根据需要选择正确的动作动词,通常是 POST
或 GET
。
一些常见的例子可能包括:
GET /search?q=devpoint
GET /verification-code?code=202106
POST /sales-tax
通常功能资源的命名模式是动词-名词的形式,有时只是动词的形式。这使得开发者很容易理解它是一个功能资源,而不是一个遵循复数名词格式的资源集合(例如项目、 账户、 客户)。
用于复杂、长时间运行的工作流的事务资源
在 SOAP
和 Web 服务时代,WS-Transaction 是一种规范,目的是将多个 Web 服务调用封装到单个分布式事务中,这将增大服务设计的复杂度。
使用基于 REST 的API设计,同样会遇到复杂的 API 设计,需要跨多个 HTTP 请求的情况。这里介绍一种易实现且灵活的解决方案。
资源的设计不仅可以表示业务对象,还可以用于表示长时间运行的事务或复杂的工作流。这些资源集合表示需要为API使用者提供的长时间运行的事务或工作流。下面来看一个酒店管理系统的预定流程:
- 首先提交一个请求来创建一个新的预订资源实例,其中包含要预定的房间详细信息,例如:
POST /reservations
- 然后,使用这个资源实例来获取或修改预订的详细信息,包括入店和离店日期、预定人数、额外请求等。这个端点可能是
PATCH /reservations/hotel66
,将根据需要经常使用它,直到预订完成了客户的所需要求 - 为了增加灵活性和审核的目的,每次进行修改时,都可以将更改获取为一个单独的嵌套资源,允许提取预订更改的历史记录,例如
GET /reservations/hotel66/modifications
- 如果有必要,可以为预订设定一个过期日期,如创建之后的1天。如果试图在过期后进行更新或支付预订费用,将收到一个
400 Bad Request
或409 Conflict
- 准备好后,客户可以为预订订单进行付款,例如 POST
/reservations/hotel66/payments
- 成功支付的结果会就意味着预订完成,需要获取相应信息,例如
GET /bookings/book66
- 预订成功之后,就需要更新订单的处理状态,例如
PATCH /bookings/book66
,如需要额外支付费用或部分或全部退款。
这是一种比较合理的设计,可以对长时间运行的事务或工作流流程建模,对资源的更改保持审核跟踪,并支持事务的过期时间。同时保持在基于 REST 的API设计的约束范围内,避免增加API使用者的复杂性。
单例资源:只能有一个
当 API 不需要资源集合时,单例资源很有用。单例资源可以代表一个虚拟资源,用于与现有资源实例直接交互,例如 GET /me
代替 GET /users/66
。
当父资源和子资源之间存在且只有一种关系时,API 可以提供嵌套的单例资源,例如 PUT /accounts/66/preferences
。
单例资源可能已经存在而无需创建它们,或者它们可能需要通过 POST 端点创建。尽管单例资源可能无法像基于集合的同类资源那样提供完整的 CRUD 风格的所有操作,但它们仍应坚持正确选择最适合必要操作的 HTTP 动作动词。
批量和批量资源操作
有些资源需要一次性批量导入大量数据,以避免出现提交100到10000次单独的POST操作,这时就需要 bulk
或 batch
处理。虽然这两个术语有时可以互换使用,但觉得应该进行区分:
bulk
批量操作在处理每个提交的记录,允许出现处理失败的,但导入的其余部分是成功,意味着在1000项记录中,可能有76条失败了,而余下的924项记录成功导入。客户端需要获取未能成功导入的记录信息以便修改再提交未能成功的记录。batch
批处理操作在单个通过或失败事务中处理所有提交的记录,意味着1000项记录中如果有76条记录失败,那么实际存储的记录就没有一条。客户需要修改76条不合格记录,再重新提交修改后的记录。
总结
当谈到基于REST的API时,很多人将利用基于 CRUD 模式的资源与REST混为一谈。REST是一组约束(规范),以便在前后端开发中协作中更加高效。对于API设计,个人建议还是需要花点时间设计,选择一个最佳的模式。