如何设计出更好的 API ?

不了解 RESR 的朋友也可以阅读

不论你是前后端开发,在程序设计的过程中或多或少会碰到API的设计,然而要设计出一个易用易维护的API也非并很容易(当然并不是每个人都这么觉得)。在从头开始创建 API 时,您需要正确获取、考虑许多细节。从基本的安全考虑到使用正确的 HTTP 方法、身份验证、决定您应该接受和返回哪些请求和响应,等等等等……不胜枚举。

那么今天我将尽我最大的努力,压缩我所知道关于什么是好的 API 的一切,和大家分享(当然并非每个人适用,所以我仅分享我的意见)。这篇文章与使用的编程语言无关,因此它们适用于任何框架或技术。

1、使用 ISO 8601 UTC日期

在处理日期和时间时,API 建议始终返回 ISO 8601 格式的字符串。

在使用时客户端应用程序对于在特定时区显示正确的日期是比较重要的,数据处理起来也比较方便。

{
    "article_writing_time": "2022-03-07T14:30:54.149Z"
}
复制代码

2、重要API制定高可用方案

对于一些重要的API,例如购物车的结算,我们不可能一个一个问题去debug,直到客服失去兴趣。

所以我建议对API 例如 GET /health 提供一个确定服务来判断其是否健康。其他应用程序(例如负载均衡器)可以调用此端点以在服务中断时采取行动。

3、接受API密钥认证

如果 API 需要由第三方调用,则通过 API 密钥进行身份验证是有意义的。

API 密钥应使用自定义 HTTP 标头(例如Api-Key)传递。它们应该有一个到期日期,并且必须可以撤销活动密钥,以便在它们受到损害时可以使它们失效。避免将 API 密钥签入源代码(类似环境变量)。

4、使用合理的HTTP方法

HTTP 方法有很多,但最重要的是:

  • POST 用于创建资源
    • POST /user
  • GET 用于阅读资源(单个资源和集合)
    • GET /user
    • GET /user/{id}
  • PATCH 用于对资源应用部分更新
    • PATCH /user/{id}
  • PUT 用于对资源应用完整更新(替换当前资源)
    • PUT /user/{id}
  • DELETE 用于删除资源
    • DELETE /user/{id}

5、使用标准化的错误响应

除了使用指示请求结果(成功或错误)的 HTTP 状态代码外,在返回错误时,始终使用标准化的错误响应,其中包含有关问题的更详细信息。

调用API的使用者总是希望每次返回的是 相同的结构并可以方便的采取相应的行动。

// Request => GET /user/uiuing.com

// Response <= 404 Not Found
{
    "code": "/user/not_found",
    "message": "找不到 ID 为 uiuing.com 的用户!"
}
复制代码
// Request => POST /register
{
	"name":"uiu",
	"webSite":"uiuing.com",
	"nickname":"我想养只猫"
}

// Response <= 400 Bad Request
{
    "code": "/register/password_required"",
    "message": "参数 [password] 是必须的!"
}
复制代码

6、使用 PATCH 代替 PUT

如前所述,PATCH 请求应该对资源应用部分更新,而 PUT 完全替换现有资源。

  • 允许任何字段不受任何限制地更新也是相当危险的;

我比较建议围绕 PATCH 做请求设计,因为:

  • 当使用 PUT 只更新资源的一部分字段时,仍然需要传递整个资源,这使得它更加网络密集且容易出错;
  • 根据我的经验,在实践中几乎不存在任何对资源进行完整更新有意义的用例。

7、使用分页

对所有返回资源集 合并,使用相同响应结构的请求进行分页。例如 page size (或类似的)来控制要检索的块。

// Request => GET /home/v1/get-business-list?page=1&size=10&username=uiu

// Response <= 200 OK
{
	"code":200,
	"message":"success",
    "page": 1,
    "size": 10,
    "data": [
        // resources
    ],
    "total_pages": 20,
    "has_previous_page": false,
    "has_next_page": true
}
复制代码

8、确保一致性

我知道这听起来好像是大家都知道的,但在实际执行过程中很难做到这一点。

好的 API 是可预测的,也就是说当调用者使用并理解这一个端口时,另一个端点最好也是以相同的方式工作。这样对于整个 API 很重要,也是衡量 API 是否设计良好和好用的关键指标之一。

  • 对字段、资源和参数使用相同的大小写
    • 对于中文转英文变量的命名工具, 你可以试试我开发的这个工具 varbook
  • 使用复数或单数资源名称(我不在乎这个,但是我会统一名称)
    • /users/{id},/orders/{id}/user/{id},/order/{id}
  • 对所有端点使用相同的身份验证和授权方法
  • 在 API 中使用相同的 HTTP 标头
    • 例如 Api-Key 用于传递 API 密钥
  • 根据响应类型使用相同的 HTTP 状态代码
    • 例如当找不到资源时 使用 404
  • 对相同类型的操作使用相同的 HTTP 方法
    • 例如 DELETE 删除资源时

9、对公共端点进行例外处理

默认情况下,每个端口都应该需要授权。大多数端口都需要调用经过身份验证的用户,因此将其设为默认值是有意义的。

如果需要公开调用端点,请显式设置此端口以允许未经授权的请求。

10、版本化 API

确保对 API 进行版本控制并在每个请求中传递版本,这样API的使用者就不会受到对另一个版本的任何更改的影响。

API 版本可以使用 HTTP 标头或查询/路径参数传递。即使是 API 的第一个版本(1.0)也应该明确地进行版本控制。

一个来自CSDN的不完整例子:

11、使用合理的 HTTP 状态码

使用传统的 HTTP 状态代码来指示请求的成功或失败。不要使用太多,并在整个 API 中为相同的结果使用相同的状态代码。

一些例子:

  • 200 取得普遍成功
  • 201 为成功创作
  • 400 对于来自客户端的错误请求
  • 401 对于未经授权的请求
  • 403 缺少权限
  • 404 对于缺少的资源
  • 429 对于太多的请求
  • 5xx 对于内部错误(应不惜一切代价避免这些错误)

12、使用不言自明的命名

大多数端口都是面向资源的,应该使用不言自明的命名,不要添加可以从其他地方推断出的不必要的信息,这也适用于字段名称。

✅建议

  • GET /user => 检索用户
  • DELETE /user/{id} => 注销用户
  • POST /user/{id}/notifications => 为特定用户创建通知
  • user.nick_name

❌不建议

  • GET /getUser
  • POST /updateUser
  • POST /notification/user
  • user.NickName

13、返回创建的资源POST

建议在POST在使用请求创建资源后返回创建的资源。这一点很重要,因为返回的创建资源将反映底层数据源的当前状态,并将包含更新的信息(例如生成的 ID)。

// Request: POST /register
{
    "email": "uiuing@foxmail.com",
    "name": "uiu",
    "web_site":"uiuing.com"
}

// Response
{
    "id": "oVUdhmcFr0",
    "email": "uiuing@foxmail.com",
    "name": "uiu",
    "web_site":"uiuing.com"
}
复制代码

14、尽可能具体

12点 所述,在设计端口、命名字段以及决定接受哪些请求和响应时,我建议你尽可能具体。如果一个 PATCH 请求只接受两个字段 (name和password),则不存在错误使用它而造成损坏数据的危险。

15、允许扩展资源

允许调用者使用名为 expand(或类似的)查询参数加载相关数据。这对于避免重复和一次性加载特定操作所需的所有数据特别有用。

// Request => GET /user/T9hoBuuTL4?expand=collect&expand=collect.items

// Response <= 200 OK
{
	 "id": "oVUdhmcFr0",
	 "email": "uiuing@foxmail.com",
	 "name": "uiu",
	 "web_site":"uiuing.com",
	 "collect": [

		 	{
				"id": "8161E0A4-0F5C-4A02-94A5-2880645A39E0",
				"items": [
					{
						"name": "如何设计出更好的 API ?"
					},
					{
						"name": "本地存储(Local Storage) 和 会话存储(Session Storage)"
					}
				]
			},
			{
				"id": "23A7A17A-086A-4638-8EED-55E5CB580856",
				"items": [
					{
						"name": "【Jquery】 入门、快速上手、DOM/Jquery对象之间互相转换"
					}
				]
			}
		]
	]
}   
复制代码


在这里插入图片描述

如果对您有帮助,可别忘了 点赞 / 收藏 / 评论 / 关注 支持下博主

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享