分享 15 个关于 REST API 设计的基本技巧

13bbd888f916074175b485919495c8c9.jpeg

英文 | https://medium.com/@liams_o/15-fundamental-tips-on-rest-api-design-9a05bcd42920

翻译 | 杨小爱

REST API 是最常见的 Web 服务类型之一,但它们也很难设计。它们允许各种客户端(包括浏览器、桌面应用程序、移动应用程序以及基本上任何具有互联网连接的设备)与服务器进行通信。因此,正确设计 REST API 非常重要,这样我们就不会在未来遇到问题。

由于需要准备的东西太多,从头开始创建 API 可能会让人不知所措。从基本安全到使用正确的 HTTP 方法、实施身份验证、决定接受和返回哪些请求和响应等等。 

在这篇文章中,我将尽我最大的努力将一些关于什么是好的 API 的强有力的建议压缩到 15 个,可以帮助你在以后的开发项目中作为一个参考意见。所有提示都与语言无关,因此它们可能适用于任何框架或技术。

1.确保在端点路径中使用名词

我们应该始终使用表示我们正在检索或操作的实体的名词作为路径名,并且始终支持使用复数名称。避免在端点路径中使用动词,因为我们的 HTTP 请求方法已经有了动词,并没有真正添加任何新信息。

该操作应由我们正在制作的 HTTP 请求方法指示。最常见的方法是 GET、POST、PATCH、PUT 和 DELETE。

  • GET 检索资源。

  • POST 向服务器提交新数据。

  • PUT/PATCH 更新现有数据。

  • DELETE 删除数据。

动词映射到 CRUD 操作。

考虑到这些原则,我们应该创建像 GET /books 这样的路由来获取书籍列表,而不是 GET /get-books 或 GET /book。

同样,POST /books 用于添加新书,PUT /books/:id 用于更新具有给定 id 的完整书籍数据,而 PATCH /books/:id 更新书籍的部分更改。 

最后,DELETE /books/:id 用于删除具有给定 ID 的现有文章。

2. JSON作为发送和接收数据的主要格式

几年前,接受和响应 API 请求主要是在 XML 中完成的。但如今,JSON(JavaScript 对象表示法)在很大程度上已成为大多数应用程序中发送和接收 API 数据的“标准”格式。 

因此,我们的第二项建议确保我们的端点返回 JSON 数据格式作为响应,并且在通过 HTTP 消息的有效负载接受信息时也是如此。

虽然 Form Data 很适合从客户端发送数据,尤其是当我们要发送文件时,但对于文本和数字来说并不理想。我们不需要表单数据来传输它们,因为对于大多数框架,我们可以直接在客户端传输 JSON。 

当从客户端接收数据时,我们需要确保客户端正确解释 JSON 数据,为此,在发出请求时应将响应头中的 Content-Type 类型设置为 application/json。

如果我们试图在客户端和服务器之间发送和接收文件,则值得再次提及异常。对于这种特殊情况,我们需要处理文件响应并将表单数据从客户端发送到服务器。

3.使用一组可预测的HTTP状态码

根据其定义使用 HTTP 状态代码来指示请求的成功或失败始终是一个好主意。不要使用太多,并且在整个 API 中为相同的结果使用相同的状态代码。一些例子是:

  • 200表示一般成功 

  • 201 表示创建成功

  • 400 来自客户端的错误请求,例如无效参数

  • 401 未授权请求

  • 403 缺少对资源的权限

  • 404 缺少资源

  • 429 请求太多

  • 5xx 表示内部错误(应尽可能避免这些错误)

根据您的用例,可能会有更多,但限制状态代码的数量有助于客户端使用更可预测的 API。

4.返回标准化消息

除了使用指示请求结果的 HTTP 状态代码之外,始终对类似端点使用标准化响应。消费者总是可以期待相同的结构并采取相应的行动。这也适用于成功,但也适用于错误消息。在获取集合的情况下,坚持使用特定格式,响应主体是否包含如下数据数组:

[  {     bookId: 1,     name: "The Republic"  },  {     bookId: 2,     name: "Animal Farm"  }]

或这样的组合对象:

{   "data": [     {       "bookId": 1,       "name": "The Republic"     },     {       "bookId": 2,       "name": "Animal Farm"     }   ],   "totalDocs": 200,   "nextPageId": 3}

无论您为此选择哪种方法,建议都是一致的。在获取对象时以及在创建和更新资源时应该实现相同的行为,通常返回对象的最后一个实例是个好主意。

// Response after successfully calling POST /books {     "bookId": 3,     "name": "Brave New World" }

虽然这不会有什么坏处,但是包含一个像“Book successfully created”这样的通用消息是多余的,因为它是从 HTTP 状态代码中隐含的。

最后但并非最不重要的一点是,错误代码在具有标准响应格式时更为重要。 

此消息应包含消费者客户端可用于向最终用户显示错误的信息,而不是我们应尽可能避免的通用“出现错误”警报。这是一个例子:

{  "code": "book/not_found",  "message": "A book with the ID 6 could not be found"}

同样,没有必要在响应内容中包含状态代码,但定义一组错误代码(如 book/not_found)很有用,以便消费者将它们映射到不同的字符串并为用户决定自己的错误消息 . 特别是对于开发/暂存环境,将错误堆栈也包含到响应中以帮助调试错误似乎就足够了。 

但请不要在生产环境中包含这些内容,因为它会产生暴露不可预测信息的安全风险。

5. 获取记录集合时使用分页、过滤和排序

一旦我们构建了一个返回项目列表的端点,就应该进行分页。集合通常会随着时间的推移而增长,因此始终返回有限且受控数量的元素非常重要。

让 API 使用者选择要获取多少对象是公平的,但预定义一个数字并为其设置最大值始终是个好主意。这样做的主要原因是返回大量数据将非常耗费时间和带宽。

要实现分页,有两种众所周知的方法:skip/limit 或 keyset。

第一个选项允许以更用户友好的方式获取数据,但通常性能较低,因为数据库在获取“底线”记录时必须扫描许多文档。 

另一方面,我更喜欢的是,键集分页接收一个标识符/id 作为参考,以在不扫描记录的情况下“剪切”一个集合或表。

按照同样的思路,API 应该提供过滤器和排序功能,以丰富数据的获取方式。为了提高性能,数据库索引成为解决方案的一部分,以通过这些过滤器和排序选项应用的访问模式最大限度地提高性能。

作为 API 设计的一部分,分页、过滤和排序的这些属性被定义为 URL 上的查询参数。例如,如果我们想要获得属于“浪漫”类别的前 10 本书,我们的端点将如下所示:

GET /books?limit=10&category=romance

6. 用PATCH代替PUT

我们不太可能需要一次完全更新完整的记录,通常会有敏感或复杂的数据,我们希望避免用户操作。考虑到这一点,PATCH 请求应该用于对资源执行部分更新,而 PUT 则完全替换现有资源。 

两者都应该使用请求体来传递要更新的信息。对于 PATCH 和 PUT 请求的完整对象,仅修改字段。

尽管如此,值得一提的是,没有什么能阻止我们使用 PUT 进行部分更新,没有“网络传输限制”可以验证这一点,这只是一个值得坚持的惯例。

7.提供扩展的响应选项

在创建可用的 API 资源和返回哪些数据时,访问模式是关键。当系统增长时,记录属性也会在该过程中增长,但并非所有这些属性始终是客户端操作所必需的。 

正是在这些情况下,提供为同一端点返回减少或完整响应的能力变得有用。如果消费者只需要一些基本字段,简化响应有助于减少带宽消耗,并可能降低获取其他计算字段的复杂性。

实现此功能的一种简单方法是提供额外的查询参数以启用/禁用扩展响应的提供。

GET /books/:id{   "bookId": 1,   "name": "The Republic"}GET /books/:id?extended=true{   "bookId": 1,   "name": "The Republic"   "tags": ["philosophy", "history", "Greece"],   "author": {      "id": 1,      "name": "Plato"   }}

8.端点责任

单一职责原则侧重于保持函数、方法或类的概念,侧重于它擅长的狭义行为。当我们考虑一个给定的 API 时,如果它只做一件事并且永不改变,我们就可以说它是一个好的 API。 

这有助于消费者更好地理解我们的 API 并使其可预测,从而促进整体集成。最好将我们的可用端点列表扩展到更多,而不是构建试图同时解决许多问题的非常复杂的端点。

9.提供准确的API文档

API 的使用者应该能够理解如何使用可用端点以及对可用端点的期望。这只有在有良好而详细的文档的情况下才有可能。 

考虑以下几个方面以提供文档完善的 API。

  • 可用的端点描述它们的目的

  • 执行端点所需的权限

  • 调用和响应示例

  • 预期的错误消息

要取得成功的另一个重要部分是在系统更改和添加之后始终使文档保持最新。实现这一目标的最佳方法是使 API 文档成为开发的基本部分。这方面的两个著名工具是 Swagger 和 Postman,它们可用于大多数 API 开发框架。

10. 使用 SSL 确保安全并配置 CORS

安全性,我们的 API 应该具备的另一个基本属性。通过在服务器上安装有效证书来设置 SSL 将确保与消费者的安全通信并防止多种潜在攻击。

CORS(跨源资源共享)是一种浏览器安全功能,可限制从浏览器中运行的脚本发起的跨源 HTTP 请求。 

如果您的 REST API 的资源接收到非简单的跨源 HTTP 请求,则需要为消费者启用 CORS 支持以进行相应操作。

CORS 协议要求浏览器向服务器发送预检请求,并在发送实际请求之前等待服务器的批准(或凭证请求)。 

预检请求作为使用 OPTIONS 方法(以及其他标头)的 HTTP 请求出现在 API 中。 

因此,为了支持 CORS,REST API 资源需要实现一个 OPTIONS 方法,该方法可以响应 OPTIONS 预检请求,至少具有 Fetch 标准规定的以下响应标头:

  • 访问控制允许方法

  • 访问控制允许标头

  • 访问控制允许来源

分配给这些键的值将取决于我们希望 API 的开放性和灵活性。我们可以分配特定的方法和已知的来源或使用通配符来开放 CORS 限制。

11. API版本

作为开发演变过程的一部分,端点开始改变并重建。但我们应该尽可能避免突然改变消费者的端点。 

将 API 视为向后兼容的资源是一个好主意,新的和更新的端点应该在不影响以前的标准的情况下可用。

这就是 API 版本控制变得有用的地方,客户端应该能够选择要连接的版本。有多种方法可以声明 API 版本控制:

1. Adding a new header "x-version=v2"2. Having a query parameter "?apiVersion=2"3. Making the version part of the URL: "/v2/books/:id"

详细了解哪种方法更方便,何时正式发布新版本以及何时弃用旧版本当然是要问的有趣问题,但不要过度扩展此项目,分析将是另一篇文章的一部分。

12.缓存数据以提高性能

为了提高我们 API 的性能,密切关注很少更改且经常访问的数据是有益的。对于这种类型的数据,我们可以考虑使用内存或缓存数据库,从而避免访问主数据库。 

这种方法的主要挑战是数据可能会过时,因此还应考虑采用最新版本的过程。

使用缓存数据对于消费者加载配置和信息目录非常有用,这些配置和目录不会随着时间的推移而改变。 

使用缓存时,请确保在标头中包含 Cache-Control 信息。这将帮助用户有效地使用缓存系统。

13.使用标准UTC日期

我想不出在某些时候不适用于日期的系统的现实。在数据级别,重要的是在客户端应用程序的日期显示方式上保持一致。

ISO 8601 是日期和时间相关数据的国际标准格式。日期应为“Z”或 UTC 格式,客户可以根据该格式为其决定时区,以防在任何情况下需要显示此类日期。这是一个关于日期应该是什么样子的例子:

{    "createdAt": "2022-03-08T19:15:08Z"}

14.健康检查端点

我们的 API 可能会在艰难时期出现故障,并且可能需要一些时间才能启动并运行它。在这种情况下,客户会希望知道服务不可用,以便他们了解情况并采取相应行动。为了实现这一点,提供一个端点(如 GET /health)来确定 API 是否健康。这个端点可以被其他应用程序调用,例如负载平衡器。我们甚至可以更进一步,通知有关 API 部分的维护期或健康状况。

15.接受API密钥认证

允许通过 API 密钥进行身份验证为第三方应用程序提供了轻松创建与我们的 API 集成的能力。

这些 API 密钥应使用自定义 HTTP 标头(例如 Api-Key 或 X-Api-Key)传递。密钥应该有一个到期日期,并且必须可以撤销它们,以便出于安全原因使它们失效。

总结

在今天这篇文章中,我与您分享了15个关于API设计的基本技巧,希望这些技巧对您有所帮助,如果您觉得有用的话,请记得点赞,我关注我,并将这篇文章分享给你的开发者朋友,也许能够帮助到他们。

最后,感谢您的阅读。

学习更多技能

请点击下方公众号

f8e734bfad7b2c0090a0cd2ee8f6168f.gif

猜你喜欢

转载自blog.csdn.net/Ed7zgeE9X/article/details/130417642