共计 15131 个字符,预计需要花费 38 分钟才能阅读完成。
当开始创立一个新零碎,或参加一个新团队或我的项目时,都会面临一个简略却粗浅的问题:这个零碎(Web Server)的 API 是否有设计规范?
image by stable difussion, prompt by alswl
这个问题困扰了我很长时间,始于我求学期间,每一次都须要与团队成员进行交换和探讨。从最后的自在格调到起初的 REST,我常常向项目组援用 Github v3 和 Foursqure API(曾经无法访问,裸露年龄)文档。然而,在实际过程中,依然会有一些与理论工作或公司通用标准不匹配的状况,这时候我须要做一些补充工作。最终,我会撰写一个简要的 DEVELOPMENT.md
文档,以形容设计方案。
但我对该文档始终有更多的想法,它还不够欠缺。因而,我想整顿出一份 <mark> 简略(Simple)而实用(Pragmatic)</mark> 的 Web API 最佳实际,也就是本文。
为什么咱们须要 API 对立标准
这个问题仿佛很显著,然而深刻分析波及团队合作效率和工程设计哲学。
API(Application Programming Interface,应用程序编程接口)是不同软件系统之间交互的桥梁。在不同软件系统之间进行通信时,API 能够通过标准化的形式进行数据传输和解决,从而实现各种应用程序的集成。
当咱们开始撰写 API 文档时,就会呈现一个范式(Design Pattern),这是显式还是隐式的,是每个人一套还是专用同一套。这就像咱们应用对立的 USB 接口一样,对立升高了老本,防止了可能存在的谬误。具体来说,这有以下几个起因:
- 容易了解,提高效率:服务提供方和生产方应用对立模式、构造和应用形式,以及对立的生产生产协定,从而缩小沟通老本。
- 专家教训:它蕴含最佳的工程实际,常见场景都有对应的解决方案,防止了每个人都要从新思考整个 API 零碎。例如,如何解决 API 缓存?如何进行鉴权?如何进行数据格式解决?
- 面向未来的扩大,须要稳固的协定:协定是形象的、独立于实现的,不是每个人都具备 设计面向不确定零碎的能力,一些宽泛应用的技术则为更宽泛的场景做了布局。
image by alswl
尽管应用对立标准的确有一些老本,须要框架性的理解和推广,但我置信在大部分场景下,对立标准所带来的收益远远高于这些老本。
然而,并非所有的状况下都须要思考 API 标准。对于一些短生命周期的我的项目、影响面十分小的外部我的项目和产品,可能并不需要过多关注标准。此外,在一些非凡的业务场景下,协定底层可能会发生变化,这时候既有的标准可能不再实用。但即使如此,我依然倡议从新起草新的标准,而不是放弃标准不顾。
标准的准则
在制订 API 标准时,咱们应该遵循一些根本准则,以应答技术上的一致,我总结了三个取得宽泛认可的准则:
- 简洁:简洁是抵制复杂性的最间接和最无效的策略,利用简洁准则升高复杂度,防止复杂性的滋生和扩散;
- 一致性:对立的设计模式和连续的设计格调有助于升高工程老本和工程师的心理累赘;
- 遵循事实:遵循现有工程畛域的形象和分层(例如 HTTP,REST,RBAC,OIDC 等),不要本人创造新的概念,要始终思考这个问题是否只有本人遇到了(答案必定是否定的)。
image by alswl
REST 到底行不行?
在 Web API 畛域,RESTful API 曾经成为广受欢迎的协定。其宽泛适用性和受众范畴之广源于其与 HTTP 协定的绑定,这使得 RESTful API 可能轻松地与现有的 Web 技术进行交互。如果您对 REST 不相熟,能够查看 阮一峰的 RESTful API 设计指南 以及 RESTful API 设计最佳实际。
REST 是一种成熟度较高的协定,Leonard Richardson 将其形容为四种成熟度级别:
image by alswl
- The Swamp of POX,应用 HTTP 承载 Legacy 协定(XML)
- Resources:应用资源形象
- HTTP Verbs:应用丰盛的 HTTP Verbs
- Hypermedia Controls:应用
rel
链接进行 API 资源整合,JSON:API 是登峰造极的体现
REST 的外围劣势在于:
- 它充分利用了 HTTP 协定的设计(HTTP Protocol)
- 它具备杰出的资源定位能力(Identification of resources)
- 它设计了齐备的资源操作形式(Manipulation of resources)
- 它具备自解释性(Self-descriptive messages)
- 它反对多种状态的出现形式(hypermedia as the engine of application state)
然而,<mark>REST 并非一种具体的协定或标准,而是一种格调理念 </mark>。只管 REST 定义了一些规定和准则,如资源的标识、对立接口、无状态通信等,但它并没有规定一种具体的实现形式。因而,在理论开发中,不同的团队可能会有不同的了解和实际,从而导致 API 的不一致性和可维护性升高。
此外,REST 也有一些局限性和缺点:
- 并非所有申请都能够用资源形容,比方登录(
/login
)操作,转换成session
就十分绕口;同样的问题在转账这种业务也会呈现。HTTP 无限的动词无奈撑持所有业务场景。 - REST 并未提供针对必然面临的问题,如分页、返回体具体构造、错误处理和鉴权等,明确的解决方案。
- 对于简单的查问(如搜寻 Search),RESTful API 的查问参数可能会变得非常复杂,难以保护。
因而,尽管 REST 格调是一个不错的指导思想,但在具体实现时须要联合具体业务需要和技术特点,有所取舍,能力实现良好的 API 设计。最初,咱们是否须要 Web API 设计规范,遵循 REST 格调呢?我认为 REST 可能解决 90% 的问题,但还有 10% 须要明确规定细节。
Web API 标准的选择题
因为咱们的协定基于 HTTP 和 REST 设计,咱们将以 HTTP 申请的四个外围局部为根底展 开探讨,这些局部别离是:URL、Header、Request 和 Response。
URL 最佳实际
我的 URL 设计启蒙来自于 Ruby on Rails。在此之前,我总是本能地将模型信息放到 URL 之上,但实际上良好的 URL 设计应该是针对零碎信息结构的布局。因而,URL 设计不仅仅要思考 API,还要思考面向用户的 Web URL。
为了达到良好的 URL 设计,我总结了以下几个规定:
- 定位资源(这就答复分页是否应该在 Header)
- 自解释(可读性强,URL 本身即蕴含外围信息)
- 平安(不能蕴含用户认证信息,OAuth 为理解这个花了很多精力,防伪造)
通常状况下,URL 的模型如下所示:
/$(prefix)/$(module)/$(model)/$(sub-model)/$(verb)?$(query)#${fragment}
其中,Prefix 可能是 API 的版本,也可能是非凡限定,如有些公司会靠此进行接入层分流;Module 是业务模块,也能够省略;Model 是模型;SubModel 是子模型,能够省略;Verb 是动词,也能够省略;Query 是申请参数;Fragment 是 HTTP 原语 Fragment。
须要留神的是,并非所有的组成部分都是必须呈现的。例如,SubModel 和 Verb 等字段可 以在不同的 URL 格调中被容许暗藏。
设计格调抉择
注: 请留神,计划 A / B / C 之间没有关联,每行高低也没有关联
问题 | 解释(见下方单列剖析) | 计划 A | 计划 B | 计划 C |
---|---|---|---|---|
API Path 外面 Prefix | /apis |
/api |
二级域名 | |
Path 外面是否蕴含 API 版本 | 版本在 URL 的劣势 | ✅ | 🚫 | |
Path 是否蕴含 Group | ✅ | 🚫 | ||
Path 是否蕴含动作 | HTTP Verb 不够用的状况 | ✅ | 🚫(纯 REST) | 看状况(如果 HTTP Verb CRUD 无奈满足就蕴含) |
模型 ID 模式 | Readable Stable Identity 解释 | 自增 ID | GUID | <mark>Readable Stable ID</mark> |
URL 中模型复数还是复数 | 复数 | 复数 | 列表复数,单向复数 | |
资源是一级(平铺)还是多级(嵌套) | 一级和多级的解释 | 一级(平铺) | 多级(嵌套) | |
搜寻如何实现,独立接口(/models/search )还是基于列表 /models/ 接口 |
独立 | 合并 | ||
是否有 Alias URL | Alias URL 解释 | ✅ | 🚫 | |
URL 中模型是否容许缩写(或精简) | 模型缩写解释 | ✅ | 🚫 | |
URL 中模型多个词语拼接的连字符 | - |
_ |
Camel | |
是否要辨别 Web API 以及 Open API(面向非浏览器) | ✅ | 🚫 |
版本在 URL 的劣势
咱们在设计 URL 时遵循一致性的准则,无论是哪种身份或状态,都会应用雷同的 URL 来拜访同一个资源。这也是 Uniform Resource Location 的根本准则。尽管咱们能够承受不同的内容格局(例如 JSON / YAML / HTML / PDF / etc),然而咱们心愿资源的地位是惟一的。
然而,问题是,对于同一资源在不同版本之间的出现,是否应该在 URL 中体现呢?这取决于设计者是否认为版本化属于地位信息的领域。
依据 RFC 的设计,除了 URL 还有 URN(Uniform Resource Name),后者是用来标识资源的,而 URL 则指向资源地址。实际上,URN 没有失去宽泛的应用,以至于 URI 简直等同于 URL。
HTTP Verb 不够用的状况
在 REST 设计中,咱们须要应用 HTTP 的 GET / POST / PUT / DELETE / PATCH / HEAD 等动词对资源进行操作。比方应用 API GET /apis/books
查看书籍列别,这个天然且正当。然而,当须要执行相似「借一本书」这样的动作时,咱们没有适合的动词(BORROW)来示意。针对这种状况,有两种可行的抉择:
- 应用 POST 办法与自定义动词,例如
POST /apis/books/borrow
,示意借书这一动作; - 创立一个借书记录,应用资源新增形式来构造不存在的动作,例如
POST /apis/books/borrow-log/
;
这个问题在简单的场景中会经常出现,例如用户登录(POST /api/auth/login
vs POST /api/session
)和帐户转账(vs 转账记录创立)等等。<mark>API 形象还是具体,始终离不开业务的解释。</mark> 咱们不能简略地将所有业务都抽象概括到 CRUD 下面,而是须要正当划分业务,以便更清晰地实现和让用户了解。
在进行设计时,咱们能够思考是否须要为每个 API 创立一个对应的按钮来不便用户的操作。如果零碎中只有一个名为 /api/do
的 API 并将所有业务都绑定在其中,尽管技术上可行,但这种设计不合乎业务需要,每一层的形象都是为了标准化解决特定问题的解法,TCP L7 设计就是这种理念的体现。
Readable Stable Identity 解释
在标记一个资源时,咱们通常有几种抉择:
- 应用 ID:ID 通常与数据库自增 ID 绑定。
- 应用 GUID:例如 UUID,只管不那么准确。
- 应用可读性和稳定性标识符(Readable Stable Identity):通常应用名称、UID 或特定 ID(如主机名、IP 地址或序列号)来标识,要求该标识符具备稳定性且全局惟一,在外部零碎中十分有用。
我集体有一个设计小技巧:应用 ${type}/${type-id}
模式的 slug 来形容标识符。Slug 是一种人类可读的惟一标识符,例如 hostname/abc.sqa
或 ip/172.133.2.1
。这种设计形式能够在可读性和唯一性之间实现很好的均衡。
A slug is a human-readable, unique identifier, used to identify a resource instead of a less human-readable identifier like an id .
from What’s a slug. and why would I use one? | by Dave Sag
PS:文章最末我还会介绍一套 Apple Music 计划,这个计划兼顾了 ID / Readable / Stable 的个性。
一级和多级的解释
URL 的层级设计能够依据建模来进行,也能够采纳间接单层构造的设计。具体问题的解决形式,例如在设计用户领有的书籍时,能够抉择多级构造的 /api/users/foo/books
或一级构造的 /api/books?owner=foo
。
技术上这两种计划都能够,<mark> 前者尊重模型的归属关系,后者则是重视 URL 构造的简略 </mark>。
多级构造更直观,但也须要解决可能存在的多种组织形式的问题,例如图书馆中书籍依照作者或类别进行组织?这种状况下,能够思考在多级构造中明确模型的归属关系,例如 /api/author/foo/books
(基于作者)或 /api/category/computer/books
(基于类别)。
Alias URL 解释
对于一些频繁应用的 URL,尽管能够依照 URL 规定进行设计,但咱们依然能够设计出一个更为简洁的 URL,以不便用户的展现和应用。这种设计在 Web URL 中尤其常见。比方一个图书馆最热门书籍的 API:
# 原始 URL
https://test.com/apis/v3/books?sort=hot&limit=10
# Alias URL
https://test.com/apis/v3/books/hot
模型缩写解释
通常,在对资源进行建模时,会应用较长的名称来命名,例如书籍索引可能被命名为 BookIndex
,而不是 Index
。在 URL 中出现时,因为 /book/book-index
的 URL 前缀蕴含了 Book,咱们能够缩小一层形容,使 URL 更为简洁,例如应用 /book/index
。这种技巧在 Web URL 设计中十分常见。
此外,还有一种模型缩写的策略,即提供一套残缺的别名注册计划。别名是全局惟一的,例如在 Kubernetes 中,Deployment 是一种常见的命名,而 apps/v1/Deployment
是通过增加 Group 限定来示意残缺的名称,同时还有一个简写为 deploy
。这个机制依赖于 Kubernetes 的 API Schema 零碎进行注册和工作。
Header 最佳实际
咱们经常会疏忽 Header 的重要性。实际上,HTTP 动词的抉择、HTTP 状态码以及各种身 份验证逻辑(例如 Cookie / Basic Auth / Berear Token)都依赖于 Header 的设计。
设计格调抉择
问题 | 解释(见下方单列剖析) | 计划 A | 计划 B | 计划 C |
---|---|---|---|---|
是否所有 Verb 都应用 POST | 对于全盘 POST | ✅ | 🚫 | |
批改(Modify)动作是 POST 还是 PATCH? | POST | PATCH | ||
HTTP Status 返回值 | 2XX 家族 | 充分利用 HTTP Status | 只用外围状态(200 404 302 等) | 只用 200 |
是否应用思考限流零碎 | ✅ 429 | 🚫 | ||
是否应用缓存零碎 | ✅ ETag / Last Modify | 🚫 | ||
是否校验 UserAgent | ✅ | 🚫 | ||
是否校验 Referrral | ✅ | 🚫 |
对于全盘 POST
有些老手(或者自认为有教训的人)可能得出一个谬误的论断,即除了 GET 申请以外,所有的 HTTP 申请都应该应用 POST 办法。甚至有些人要求 所有行为(即便是只读的申请)也应该应用 POST 办法。这种观点通常会以“简略统一”、“防止缓存”或者“运营商的要求”为由来反对。
然而,咱们必须明确 HTTP 办法的设计初衷:它是用来形容资源操作类型的,从而派生出了包含缓存、平安、幂等性等一系列问题。在绝对简略的场景下,省略掉这一层形象确实不会带来太大的问题,但一旦进入到简单的畛域中,应用 HTTP 办法这一层形象就显得十分重要了。<mark> 这是否遵循规范将决定你是否可能取得标准化带来的益处 </mark>,类比一下就像一个新的手机厂商能够抉择不应用 USB TypeC 接口。技术上来说是可行的,但同时也失去了很多标准化反对和大家心智上的约定俗成。
我特地喜爱一位 知乎网友 的 评论:「<mark> 路由没有隐没,只是转移了 </mark>」。
2XX 家族
HTTP 状态码的用处在于表明客户端与服务器间通信的后果。2XX 状态码系列代表服务器曾经胜利接管、了解并解决了客户端申请,回应的内容是胜利的。以下是 2XX 系列中常见的状态码及其含意:
- 200 OK:申请已胜利解决,服务器返回了响应。
- 201 Created:申请曾经被胜利解决,并且在服务器上创立了一个新的资源。
- 202 Accepted:申请已被服务器承受,但尚未执行。该状态码通常用于异步解决。
- 204 No Content:申请已胜利解决,然而服务器没有返回任何响应体内容。
2XX 系列的状态码示意申请已被胜利解决,这些状态码能够让客户端明确通晓申请已被正确处理,从而进行下一步操作。
是否须要全面应用 2XX 系列的状态码,取决于是否须要向客户端明确 / 显示的信息,告知它下一步动作。如果曾经通过其余形式(包含文档、口头协定)形容分明,那么的确能够通盘应用 200 状态码进行返回。但基于行为传递含意,或是基于文档(甚至口头协定)传递含意,哪种更优良呢?是更为简单还是更为简洁?
Request 最佳实际
设计格调抉择
问题 | 解释(见下方单列剖析) | 计划 A | 计划 B | 计划 C |
---|---|---|---|---|
简单的参数是放到 Form Fields 还是独自一个 JSON Body | Form Fields | Body | ||
子资源是一次性查问还是独立查问 | 嵌套 | 独立查问 | ||
分页参数寄存 | Header | URL Query | ||
分页形式 | 分页形式解释 | Page based | Offset based | Continuation token |
分页控制者 | 分页管制着解释 | 客户端 | 服务端 |
分页形式解释
咱们最为常见的两种分页形式是 Page-based 和 Offset-based,能够通过公式进行映射。此外,还存在一种称为 Continuation Token 的形式,其技术相似于 Oracle 的 rownum 分页计划,应用参数 start-from=?
进行形容。尽管 Continuation Token 的优缺点都非常突出,应用此种形式能够将程序性用于代替随机性。
分页管制着解释
在某些状况下,咱们须要辨别客户端分页(Client Pagination)和服务器分页(Server Pagniation)。客户端分页是指下一页的参数由客户端计算而来,而服务器分页则是由服务器返回 rel
或 JSON.API 等协定。应用服务器分页能够防止一些问题,例如批量屏蔽了一些内容,如果应用客户端分页,可能会导致缺页或者白屏。
Response 最佳实际
设计格调抉择
问题 | 解释(见下方单列剖析) | 计划 A | 计划 B | 计划 C |
---|---|---|---|---|
模型出现品种 | 模型的几种模式 | 繁多模型 | 多种模型 | |
大模型如何蕴含子模型模型 | 模型的连贯、侧载和嵌入 | 嵌入 | 外围模型 + 屡次关联资源查问 | 链接 |
字段返回是按需还是归并还是对立 | 对立 | 应用 fields 字段按需 |
||
字段体现格局 | Snake | Camel | ||
错误码 | 无自定,应用 Message | 自定义 | ||
谬误格局 | 全局对立 | 按需 | ||
时区 | UTC | Local | Local + TZ | |
HATEOAS | ✅ | 🚫 |
模型的几种模式
在 API 设计中,对于模型的表现形式有多种定义。尽管这并不是 API 标准必须探讨的话题,但它对于 API 设计来说是十分重要的。
我将模型常说的模型出现形式分为一下几类,这并非是业余的界定,借用了 Java 语境上面的一些定义。这些名称在不同公司甚至不同团队会有不一样的叫法:
image by alswl
- Business Object(BO):原始的业务模型
- Data Object(DO):存储到 RDBMS 的模型,所以必须是打平的字段构造,有时候一个 BO 会对应到多个 DO
- View Object(VO):出现到体现层的模型,只保留用户须要看到信息,比方会去掉敏感信息
- Data Transfer Object(DTO):用来在 RPC 零碎进行传输的模型,个别和 原始的 Model 差别不大,依据不同序列化零碎会有差别(比方枚举的解决)
除此之外,还常常应用两类:Rich Model 和 Tiny Model(请疏忽命名,不同团队叫法差别比拟大):
- Rich Model:用来形容一个丰盛模型,这个模型蕴含了简直所有须要用的的数据,也容许子资源进行嵌套
- Tiny Model:是一个精简模型,往往用来在列表 API 外面被应用
模型的连贯、侧载和嵌入
在 API 设计中,咱们常常须要解决一个模型中蕴含多个子模型的状况,例如 Book 蕴含 Comments。对于这种状况,通常有三种表现形式可供选择:链接(Link)、侧载(Side)和嵌入(Embed)。
image by alswl
链接(有时候这个 URL 也会暗藏,基于客户端和服务端的隐式协定进行申请):
{
"data": {
"id": 42,
"name": "朝花夕拾",
"relationships": {
"comments": "http://www.domain.com/book/42/comments",
"author": ["http://www.domain.com/author/ 鲁迅"]
}
}
}
侧载:
{
"data": {
"id": 42,
"name": "朝花夕拾",
"relationships": {
"comments": "http://www.domain.com/book/42/comments",
"authors": ["http://www.domain.com/author/ 鲁迅"]
}
},
"includes": {
"comments": [
{
"id": 91,
"author": "匿名",
"content": "十分棒"
}
],
"authors": [
{
"name": "鲁迅",
"description": "鲁迅原名周树人"
}
]
}
}
嵌入:
{
"data": {
"id": 42,
"name": "朝花夕拾",
"comments": [
{
"id": 91,
"author": "匿名",
"content": "十分棒"
}
],
"authors": [
{
"name": "鲁迅",
"description": "鲁迅原名周树人"
}
]
}
}
其余
还有一些问题没有收敛在四因素外面,然而咱们在工程实际中也常常遇到,我将其捋进去:
我不是 HTTP 协定,怎么办?
Web API 中较少遇到非 HTTP 协定,新建一套协定的老本太高了。在某些特定畛域会引入一些协定,比方 IoT 畛域的 MQTT。
此外,RPC 是一个波及宽泛畛域的概念,其内容远远不止于协定层面。通常咱们会将 HTTP 和 RPC 的传输协定以及序列化协定进行比照。我认为,本文中的许多探讨也对 RPC 畛域具备重要意义。
有些团队或集体打算应用本人创立的协定,但我的观点是应尽量避免自建协定,因为真正须要创立协定的状况十分常见。如果的确存在强烈的须要,那么我会问两个问题:是否通读过 HTTP RFC 文档和 HTTP/2 RFC 文档?
我不是近程服务(RPC / HTTP 等),而是 SDK 怎么办?
本文次要探讨的是 Web API(HTTP)的设计规范,并且其中一些规定能够借鉴到 RPC 零碎中。然而,探讨的根底都是建设在近程服务(Remote Service)的根底之上的。如果你是 SDK 开发人员,你会有两个角色,可能会作为客户端和近程服务器进行通信,同时还会作为 SDK 提供面向开发人员的接口。对于后者,以下几个标准能够作为参考:
后者能够参考一下这么几个标准:
- Azure SDK 设计规范 General Guidelines: API Design | Azure SDKs
- Posix API 范例(比方 File Low-Level I/O (The GNU C Library))
认证鉴权计划
一般而言,Web API 设计中会明确形容所采纳的认证和鉴权零碎。须要留神辨别「认证」和「鉴权」两个概念。对于「认证」这一话题,能够在独自的章节中进行探讨,因而本文不会开展这一方面的内容。
在 Web API 设计中,常见的认证形式包含:HTTP Basic Auth、OAuth2 和账号密码登录等。罕用的状态治理形式则有 Bearer Token 和 Cookie。此外,在防篡改等方面,还会采纳基于 HMac 算法的防重放和篡改计划。
疏忽掉的话题
在本次探讨中,我未波及以下话题:异步协定(Web Socket / Long Pulling / 轮训)、CORS、以及平安问题。尽管这些话题重要,然而在本文中不予开展。
什么时候突破规定
有些开发者认为规定就是为了突破而存在的。事实往往非常复杂,咱们难以探讨分明各个细节。如果开发者感觉规定不符合实际需要,有两种解决形式:批改规定或突破规定。然而,我更偏向于探讨和更新规定,明确标准不足之处,确定是否存在非凡状况。如果的确须要创立特例,肯定要在文档中详细描述,告知接任者和消费者这是一个特例,阐明特例产生的起因以及特例是如何应答的。
一张格调 Checklist
Github 格调
Github 的 API 是我经常参考的对象。它对其业务领域建模十分清晰,提供了详尽的文档,使得沟通老本大大降低。我次要参考以下两个链接:API 定义 GitHub REST API documentation 和 面向应用程序提供的 API 列表 Endpoints available for GitHub Apps,该列表简直蕴含了 Github 的全副 API。
问题 | 抉择 | 备注 |
---|---|---|
URL | ||
API Path 外面 Prefix | 二级域名 | https://api.github.com |
Path 外面是否蕴含 API 版本 | 🚫 | Header X-GitHub-Api-Version API Versions |
Path 是否蕴含 Group | 🚫 | |
Path 是否蕴含动作 | 看状况(如果 HTTP Verb CRUD 无奈满足就蕴含) | 比方 PUT /repos/{owner}/{repo}/pulls/{pull_number}/merge POST /repos/{owner}/{repo}/releases/generate-notes |
模型 ID 模式 | Readable Stable Identity | |
URL 中模型复数还是复数 | 复数 | |
资源是一级(平铺)还是多级(嵌套) | 多级 | |
搜寻如何实现,独立接口(/models/search )还是基于列表 /models/ 接口 |
独立 | |
是否有 Alias URL | ? | |
URL 中模型是否容许缩写(或精简) | 🚫 | 没有看到显著信息,基于多级模型也不须要,然而存在 GET /orgs/{org}/actions/required_workflows |
URL 中模型多个词语拼接的连字符 | - 和 _ |
GET /repos/{owner}/{repo}/git/matching-refs/{ref} vs GET /orgs/{org}/actions/required_workflows |
是否要辨别 Web API 以及 Open API(面向非浏览器) | 🚫 | |
Header | ||
是否所有 Verb 都应用 POST | 🚫 | |
批改(Modify)动作是 POST 还是 PATCH? | PATCH | |
HTTP Status 返回值 | 充分利用 HTTP Status | 罕用,包含限流洗损 |
是否应用思考限流零碎 | ✅ 429 | |
是否应用缓存零碎 | ✅ ETag / Last Modify | Resources in the REST API#client-errors |
是否校验 UserAgent | ✅ | |
是否校验 Referrral | 🚫 | |
Request | ||
简单的参数是放到 Form Fields 还是独自一个 JSON Body | Body | 参考 Pulls#create-a-pull-request |
子资源是一次性查问还是独立查问 | 嵌套 | 从 Pulls 进行判断 |
分页参数寄存 | URL Query | |
分页形式 | Page | Using pagination in the REST API |
分页控制者 | 服务端 | 同上 |
Response | ||
模型出现品种 | 多种模型 | 比方 Commits 外面的 明细和 Parent Commits |
大模型如何蕴含子模型模型 | 外围模型 + 屡次关联资源查问? | 没有明确阐明,依据几个外围 API 反推 |
字段返回是按需还是归并还是对立 | 对立 | |
字段体现格局 | Snake | |
错误码 | 无 | Resources in the REST API#client-errors |
谬误格局 | 全局对立 | Resources in the REST API#client-errors |
时区 | 复合计划(ISO 8601 \> Time-Zone Header \> User Last \> UTC) | Resources in the REST API#Timezones |
HATEOAS | 🚫 |
Azure 格调
Azure 的 API 设计遵循 api-guidelines/Guidelines.md at master · microsoft/api-guidelines,这篇文章偏原理性,另外还有一份实用领导手册在 Best practices in cloud applications 和 Web API design best practices。
须要留神的是,Azure 的产品线远比 Github 丰盛,一些 API 也没有遵循 Azure 本人的标准。在找实例时候,我次要参考 REST API Browser,Azure Storage REST API Reference。如果具体实现和 Guidelines.md 抵触,我会采纳 Guidelines.md 论断。
问题 | 抉择 | 备注 |
---|---|---|
URL | ||
API Path 外面 Prefix | 二级域名 | |
Path 外面是否蕴含 API 版本 | 🚫 | x-ms-version |
Path 是否蕴含 Group | ✅ | |
Path 是否蕴含动作 | 🚫? | 没有明确阐明,然而有偏向应用 comp 参数来进行动作,放弃 URL 的 RESTful 参考 Lease Container (REST API) – Azure Storage |
模型 ID 模式 | Readable Stable Identity | Guidelines.md#73-canonical-identifier |
URL 中模型复数还是复数 | 复数 | Guidelines.md#93-collection-url-patterns |
资源是一级(平铺)还是多级(嵌套) | 多级 / 一级 | api-design#define-api-operations-in-terms-of-http-methods,注 MS 有 comp=? 这种参数,用来解决特地的命令 |
搜寻如何实现,独立接口(/models/search )还是基于列表 /models/ 接口 |
? | 偏向于基于列表,因为大量应用 comp= 这个 URL Param 来进行子命令,比方 Incremental Copy Blob (REST API) – Azure Storage |
是否有 Alias URL | ? | |
URL 中模型是否容许缩写(或精简) | ? | |
URL 中模型多个词语拼接的连字符 | Camel | Job Runs – List – REST API (Azure Storage Mover) |
是否要辨别 Web API 以及 Open API(面向非浏览器) | 🚫 | |
Header | ||
是否所有 Verb 都应用 POST | 🚫 | |
批改(Modify)动作是 POST 还是 PATCH? | PATCH | Agents – Update – REST API (Azure Storage Mover) |
HTTP Status 返回值 | 充分利用 HTTP Status | Guidelines.md#711-http-status-codes |
是否应用思考限流零碎 | ? | |
是否应用缓存零碎 | ✅ | Guidelines.md#75-standard-request-headers |
是否校验 UserAgent | 🚫 | |
是否校验 Referrral | 🚫 | |
Request | ||
简单的参数是放到 Form Fields 还是独自一个 JSON Body | Body | 参考 Agents – Create Or Update – REST API (Azure Storage Mover) |
子资源是一次性查问还是独立查问 | ? | |
分页参数寄存 | ? | 没有论断 |
分页形式 | Page based | |
分页控制者 | 服务端 | Agents – List – REST API (Azure Storage Mover) |
Response | ||
模型出现品种 | 繁多模型 | 揣测 |
大模型如何蕴含子模型模型 | ? | 场景过于简单,没有繁多论断 |
字段返回是按需还是归并还是对立 | ? | |
字段体现格局 | Camel | |
错误码 | 应用自定错误码清单 | 至多在各自产品内 |
谬误格局 | 自定义 | |
时区 | ? | |
HATEOAS | ? | api-design#use-hateoas-to-enable-navigation-to-related-resources |
Azure 的整体设计格调要比 Github API 更简单,同一个产品的也有多个版本的差别,看 下来统一性要更差一些。这种简单场景想用繁多的标准束缚所有团队确实也是更艰难的。咱们能够看到 Azaure 团队在 Guidelines 下面致力,他们最近正在推出 vNext 标准。
我个人风格
我个人风格根本继承自 Github API 格调,做了一些微调,更适宜中小型产品开发。我的改变起因都在备注中解释,改变出发点是:简化 / 缩小歧义 / 思考实际成本。如果备注外面标记了「注」,则是遵循 Github 计划并增加一些观点。
问题 | 抉择 | 备注 |
---|---|---|
URL | ||
API Path 外面 Prefix | /apis |
咱们往往只有一个零碎,一个域名要承载 API 和 Web Page |
Path 外面是否蕴含 API 版本 | ✅ | |
Path 是否蕴含 Group | ✅ | 做一层业务模块拆分,隔离肯定单干边界 |
Path 是否蕴含动作 | 看状况(如果 HTTP Verb CRUD 无奈满足就蕴含) | |
模型 ID 模式 | Readable Stable Identity | |
URL 中模型复数还是复数 | 复数 | |
资源是一级(平铺)还是多级(嵌套) | 多级 + 一级 | 注:80% 状况都是遵循模型的归属,大量状况(常见在搜寻)应用一级 |
搜寻如何实现,独立接口(/models/search )还是基于列表 /models/ 接口 |
对立 \> 独立 | 低成本实现一些(晚期 Github Issue 也是没有 /search 接口 |
是否有 Alias URL | 🚫 | 简略点 |
URL 中模型是否容许缩写(或精简) | ✅ | 一旦做了精简,须要在术语表标记进去 |
URL 中模型多个词语拼接的连字符 | - |
|
是否要辨别 Web API 以及 Open API(面向非浏览器) | 🚫 | |
Header | ||
是否所有 Verb 都应用 POST | 🚫 | |
批改(Modify)动作是 POST 还是 PATCH? | PATCH | |
HTTP Status 返回值 | 充分利用 HTTP Status | |
是否应用思考限流零碎 | ✅ 429 | |
是否应用缓存零碎 | 🚫 | 简略一些,应用动态数据,去除缓存能力 |
是否校验 UserAgent | ✅ | |
是否校验 Referrral | 🚫 | |
Request | ||
简单的参数是放到 Form Fields 还是独自一个 JSON Body | Body | |
子资源是一次性查问还是独立查问 | 嵌套 | |
分页参数寄存 | URL Query | |
分页形式 | Page | |
分页控制者 | 客户端 | 升高服务端老本,容忍极其状况空白 |
Response | ||
模型出现品种 | 多种模型 | 应用的 BO / VO / Tiny / Rich |
大模型如何蕴含子模型模型 | 外围模型 + 屡次关联资源查问 | |
字段返回是按需还是归并还是对立 | 对立 | Tiny Model(可选)/ Model(默认)/ Rich Model(可选) |
字段体现格局 | Snake | |
错误码 | 无 | 注:很多场景只有 message |
谬误格局 | 全局对立 | |
时区 | ISO 8601 | 只应用一种格局,不再反对多种计划 |
HATEOAS | 🚫 |
题外话 – Apple Music 的一个乏味设计
image from Apple Music
我最近在应用 Apple Music 时留神到了其 Web 页面的 URL 构造:
/cn/album/we-sing-we-dance-we-steal-things/277635758?l=en
认真看这个 URL 构造,能够发现其中 Path 蕴含了人类可读的 slug,分为三个局部:alumn/$(name)/$(id)
(其中蕴含了 ID)。我立刻想到了一个问题:两头的可读名称是否无机器意义,纯正面向自然人?于是我测试了一个捏造的地址:/cn/album/foobar/277635758?l=en
。在您尝试拜访之前,您能猜出后果是否能够拜访吗?
这种设计范式比我当初罕用的 URL 设计规范要简单一些。我的标准要求将资源定位应用两层 slug 组织,即 $(type)/$(id)
。而苹果应用了 $(type)/(type-id)/$(id)
,同时关照了可读性和准确性。
题外话 – 为什么 GraphQL 不行
GraphQL 是一种通过应用自定义查询语言来申请 API 的形式,它的长处在于能够提供更灵便的数据获取形式。相比于 RESTful API 须要一次申请获取所有须要的数据,GraphQL 容许客户端明确指定须要的数据,从而缩小不必要的数据传输和解决。
然而,GraphQL 的过于灵便也是它的毛病之一。因为它没有像 REST API 那样有一些业务场景建模的标准,开发人员须要本人思考数据的解决形式。这可能导致一些不合理的查问申请,对后端数据库造成适度的压力。此外,GraphQL 的实现和文档绝对较少,也须要更多的学习老本。
因而,尽管 GraphQL 能够在一些特定的场景下提供更好的成果,但它并不适宜所有的 API 设计需要。实际上,一些公司甚至抉择放弃反对 GraphQL,例如 Github 的 一些我的项目。
最初
Complexity is incremental(复杂度是递增的)– John Ousterhout (via)
<mark> 格调没有最好,只有最适宜,然而领有格调是很重要的。</mark>
建设一个优良的规定不仅须要对现有机制有粗浅的了解,还须要对业务畛域有全面的把握,并在团队内进行无效的合作与沟通,推广并施行规定。不过,一旦规定建设起来,就可能无效升高零碎的复杂度,防止随着工夫和业务的推动而一直减少的复杂性,并缩小研发方面的沟通老本。
这是一项长期的投资,但可能取得长久的回报。心愿有久远眼光的人可能留神到这篇文章。
次要参考文档:
- api-guidelines/Guidelines.md at master · microsoft/api-guidelines
- GitHub’s APIs
- Web API design best practices – Azure Architecture Center | Microsoft Learn
- API 设计最佳实际的思考 – 谷朴
原文链接: https://blog.alswl.com/2023/04/web-api-guidelines/
3a1ff193cee606bd1e2ea554a16353ee
欢送关注我的微信公众号:窥豹