为了账号安全,请及时绑定邮箱和手机立即绑定

Tinder API 风格指南(第一部分)

标签:
API

作者: Nishant Mittal

顾问团队: Vijaya Vangapandu, Devin Thomson, Serge Vartanov,和 Greg Giacovelli

API 风格指南的贡献者: Nishant Mittal, Xing Wei,和 Felix Changoo

在过去十年中,Tinder 经历了用户数量的指数级增长。今天,该应用每天处理超过 10 亿次点赞和滑屏操作。这种快速扩张伴随着众多新奇体验的推出,例如 AI 照片推荐、配对助手、分享我的约会、照片和身份验证、旅行伴侣和探索模式 等等。

为了支持这一规模和复杂性,Tinder 进行了重大的组织转型。最初,只有几个团队负责管理核心业务,但公司随后演变为由专注于特定领域的专业团队组成的结构。这些团队包括信任与安全、身份、参与度、营收、推荐、聊天、个人资料、匹配名单、机器学习和基础设施。

当新团队在各自领域内建立并开始开发服务和功能时,每个团队自然地发展和采用了自己的设计和开发实践。这种多样性导致了不同领域服务之间存在显著差异。以下是一些问题。

不一致的错误情况

自主运行的团队导致了服务通信的不一致性。一些团队使用了标准HTTP状态码(2xx,4xx,5xx),而另一些团队则在200状态码下返回了自定义错误码(例如:40001,40301,50001等)。这使客户端应用程序感到困惑,并让错误处理更加复杂,还影响了依赖标准码进行自动监控和日志记录系统的可靠性。

缺乏模式管理
  1. 协作构建中的挑战: 在后端和客户端团队(iOS、Android、Web)之间的协作中遇到了挑战。后端团队在发布新端点时经常为他们的服务定制模式,这使得客户端团队难以重用模式。由于反馈延迟,客户端团队无法提出模式更改建议,导致他们创建了自己的模型。
  2. 缺乏单一事实来源: 当客户端维护自己版本的数据模型时,系统中就会出现多个略有不同的模型版本。这种碎片化导致没有单一的权威来源,使得增加了维护工作的复杂性,减缓了功能发布速度。此外,文档中描述的内容与各平台实现的内容之间的差异导致了潜在的错误、沟通不畅和系统整体更不可靠。

其他问题还包括使用不一致的版本策略来发布API的新版本、以不可预测的方式设计模糊描述API目的的URI,以及设计不佳的API合约。

为解决这些不一致的问题,制定全面的文档来规范API开发实践,并通过定期的手动和系统性审计来确保采用这些标准实践变得至关重要。

Tinder API 规范

几乎所有的 Tinder 端点都设计为 RESTful API(表示状态转移的),允许在我们的平台上实现可扩展且灵活的交互。本文将探讨我们遵循的关键标准,以创建稳健且符合 RESTful 原则的 API。

设计URI
URI 统一资源标识符 模式

URI对于传达API的特性和行为至关重要,因此在设计URI时需要谨慎。我们建议尽可能遵循以下模式,以确保URI设计既清晰又有效。

[https://service-mesh-host-name/[版本]/[服务名称]/[资源]/[子资源](https://service-mesh-host-name/\[版本\]/\[服务名称\]/\[资源\]/\[子资源\])**_]_**

小提示:
注意:根据具体上下文,可以选择"注意:" 或 "小提示:",这里采用"小提示:"以符合更随意的语境。如果上下文允许,也可以直接省略冒号。

  • 如果 API 的服务名称和资源相同,URI 只应包含其中一个(例如,v2/themes,而不是 v2/themes/themes)。

  • URI 应该 清楚地反映 API 的功能。尽管这种模式是一个很好的指引,但它并不是一个严格的规定——这只是适用于大多数 URI 的一种通用方式。
路径前缀 — 版本前缀

版本管理是一种管理API更改的实践,以不干扰客户端为前提。它有助于清晰地传达所做的更改,让消费者自行决定何时升级到最新版本。

版本策略规划

在Tinder,我们使用带有路径前缀的版本标识(例如,v1, v2)。我们的版本策略是:上一个版本的API会一直保持可用状态,直到所有客户端都升级到最新版本。当发布新版本时,为上一个版本创建一个淘汰计划,清楚地向客户告知时间表,并确保他们与迁移计划保持一致步调。如有必要,调整计划,并继续支持旧版本,直到所有客户端都迁移到新版本。

什么时候发布API的新版本?

API版本更新应仅在需要客户端更新代码的破坏性变更时进行。非破坏性变更应包含在现有API版本中,允许客户端自行选择采用这些变更。

让我们来看看一些情况来决定什么时候发布新的API版本:

场景1:迁移
当从一个服务迁移到另一个服务(比如 serviceV1到serviceV2)时,无需创建新的API版本。只需更改实现而不改变版本号,因为这样不会对客户端造成任何破坏性影响。

场景2:非破坏性变更
添加新的字段、方法(如 POST 和 PUT)或端点时,这被视为非破坏性变更。

  • 新增字段: 在现有 API 中添加新字段时不需要增加版本号。
  • 新增方法或新端点: 因为没有旧版本,使用 /v1 作为版本规范。

场景3:破坏性变更
删除必填字段、更改字段类型或重命名字段都是破坏性变更,会影响客户端。因此,需要创建一个新的API版本。

注意:

  • 端点的版本管理与该服务下的服务版本和其他端点的版本无关
  • 每个端点的版本管理都是独立的。

比如说,如果一个服务下面有两个端点(例如:recs)。每个端点可以各自遵循自己的版本控制,如下所示,显示了每个端点的当前版本。

  • GET /v2/recs/profiles (获取用户资料推荐)
  • GET /v3/recs/core (获取核心推荐数据)

在上面的例子中,/v2/recs/profiles 是版本 v2,意味着其当前版本是 v2,并且在某个时候曾经有过版本 v1。/v3/recs/core 是版本 v3,意味着其当前版本是 v3,并且此 API 已经进行了两次重大改动。

注意:版本指南
  1. 仅在破坏性变更时发布新版本: 仅在破坏性变更时发布新版本;将非破坏性变更包含在当前版本中。
  2. 清晰记录每个API版本的变更: 通过API文档(例如,Swagger)清晰记录每个API版本的变更。
  3. 及时与客户沟通迁移时间表: 直到所有客户完成迁移,保持旧版本的可用状态。
路径规范——服务、资源及其子资源的命名约定

URI的路径包括服务名、资源以及子资源。下面举个例子:

关于 URI 路径规划的重要指南
  1. 保持路径段简洁: 确保 URI 路径段简洁易懂。
  2. 使用复数名称表示资源:使用复数形式表示资源集合(如 /resources)。这样约定表示该端点代表所有资源。

例如,执行 GET /resources 通常会返回整个集合。而 GET /resources/{id} 则用于检索集合中的特定资源。此外,POST /resources 则用于向集合添加新项目,这进一步强调了 URI 代表一组相关实体的概念。

  1. 遵循骆驼串命名法: 骆驼串命名法通过用短横线(-)把单词分开,使URI更易读。因此,URI路径应采用骆驼串命名法(例如,word1-word2)。
  2. 避免在URI里加具体的文件扩展名: 不要在URI中包含特定实现的扩展名(例如,.php,.html,.py)。

以下是一个根据上述指南形成的URI示例:

GET /v1/同意管理/同意/{consent-id}

在这个例子中,v1 是端点的版本,consent-management 是服务名,consents 是一个资源类型。这个 API 将从 consents 集合中获取与特定的 consent-id 相对应的特定的同意书。

关于 URI 中名词的使用

设计URI时,应使用名词来表示资源和其子资源,而不是动词。这些HTTP方法(GET、PATCH、POST、PUT、DELETE)已经定义了对资源的各种操作(创建(C)、读取(R)、更新(U)、删除(D))。在URI中包含像“get”、“update”或“delete”这样的动词会让HTTP方法显得多余。

路径尾缀 — 路径相关的参数和查询参数
路径设置

路径参数是用来定位特定资源的,下面举几个使用路径参数的具体例子。

GET /v1/passport/locations/{位置ID}

在这个例子中,passport 是服务名,locations 是资源,location-id 是路径参数,用来指定一个特定的位置。URI 中也可以有多个路径参数。

GET请求 /v1/passport/locations/{location-id}/profiles/{profile-id}接口

在此示例中,location-idprofile-id 均为路径参数。它们用于定位特定位置的特定档案配置。另外要注意的是,locations 是一个资源,而 profiles 是位置下的子资源。

查询参数(query参数)

查询参数主要用于提供额外的查询条件,即在查询时添加额外条件。下面举几个使用查询参数的例子。

GET /v1/users?status=active

如上例所示,我们正在尝试找到活跃的用户。

GET /v1/users?status=active&days=20

如上所述,可以有多个查询参数值。

关于路径和查询参数的重要指南
  • 不要用查询参数来改变资源状态: 不要用查询参数来改变资源的状态。比如,使用查询参数创建用户是一个不好的例子,比如:GET /v1/users?mode=create
  • 用查询参数来做数据的过滤、排序、搜索和分页: 用查询参数来过滤、排序、搜索和分页。
  • 确保查询和路径参数简洁明了: 确保查询和路径参数简洁明了。
  • 不要在查询或路径参数里包含敏感信息: URL经常会被记录在服务器日志、浏览器历史记录和第三方监控工具里。在URI中包含敏感信息会增加数据泄露的风险并且增加安全风险。
HTTP 方法:

HTTP 请求中常用的方法,如 GET 和 POST。

RESTful API 使各种网络应用程序能够支持 CRUD 操作(创建、读取、更新、删除)。根据以下信息为 API 执行的操作选择合适的 HTTP 方法。

一个例外:何时在资源检索中使用 POST 而不是 GET?

在某些情况下,团队可能选择使用POST HTTP方法而不是GET来获取资源信息。这种方法虽然在获取操作中并不常见,但有时需要满足特定要求。为了清晰和自我说明,任何用于查询的POST请求都应该在其URI路径末尾添加/query,使其目的更加明确。

在什么情况下使用 POST 而不是 GET

案例1:查询的数据比较复杂,或者数据量特别大

当查询参数复杂或内容较多时,GET 请求可能由于 URI 长度限制或无法在头部包含大量数据而不适合。例如,一次性请求多个资源的信息或传递复杂的数据结构可能会超出 URI 的实际限制。在这种情况下,使用 POST 请求更为合适,因为它允许将数据包含在请求体中,从而避免了 GET 请求的长度限制。

案例2: 处理敏感信息的情况

GET 请求会在 URI 中添加查询参数,这可能导致敏感信息的泄露。服务器和中间代理常常会记录 URI,因此,如果 GET 请求中包含敏感信息,可能会带来安全风险。为减轻这一风险,可以使用 POST 请求,将敏感查询数据放在请求体里,而不是放在 URI 中,从而减少暴露。在这种情况下,使用 POST 方法加上 /query 路径可以让意图更加明确,虽然主要操作仍然是获取数据。

最佳做法
  • URI长度:在决定使用GET还是POST时,要注意不同浏览器和服务器支持的最大URI长度。
  • 幂等性:记住,虽然GET请求在定义上是幂等的,但POST请求本身不具备幂等性。如果客户端使用场景需要幂等性,确保API的设计可以支持幂等性。
疑问:什么时候在删除资源时使用 POST 而不是 DELETE?
疑问:使用 POST 而不是 DELETE 删除资源的时机是什么时候?

(Note: Both versions are stylistically appropriate, but the second one sounds more natural in Chinese.)

最终选择更自然流畅的版本:

疑问:使用 POST 而不是 DELETE 删除资源的时机是什么时候?

根据RFC 7231, 第4.3.5节的最新更新,不建议使用DELETE方法携带请求体。这种限制在删除操作需要客户端指定复杂的条件或额外的信息时会带来挑战。而这些信息无法仅通过URL参数传达。在这种情况下,使用POST方法可以是一个实用的替代选择。

一些使用 POST 而不是 DELETE 的场景

当 DELETE 操作如果需要传递复杂的对象数据,例如定义了具体删除条件的详细对象时,建议使用 POST 请求。为了更明确地表达操作的目的,建议将路径后缀改为 /deletion。这样做可以让操作更加直观,让开发者能够轻松理解这是一个与删除相关的操作。

最佳做法
  • 幂等性:虽然 DELETE 操作通常具有幂等性,但 POST 请求却没有。请确保 POST 请求背后的删除逻辑能够处理重复请求而不会产生意外副作用。
  • URI 路径命名:对于此类操作始终使用 /deletion 后缀有助于保持 API 的清晰性,让开发人员更容易理解和正确使用这些端点。
  • 文档:请清楚记录为什么使用 POST 而不是 DELETE,包括请求体的结构以及与 /deletion 端点相关的任何特殊行为。
请求头信息
常见的请求头

标准请求头是一组预定义的头部,在通过HTTP的RESTful API中使用,用于传达重要元数据并控制客户端和服务器在请求响应周期中的行为。这有助于客户端和服务器之间的一致性和可预测的交互,从而确保双方遵循预期的协议和标准。

以下是一些標準標頭的例子:

自定义请求首部

自定义头部是特定于某个系统、应用程序或组织的非标准HTTP头部。这些头部通常用于传输特定于应用程序的元数据或控制信息,这些信息并未包含在标准HTTP头部中。自定义头部允许开发人员扩展HTTP,以满足其应用程序的具体需求。

Tinder 自定义头字段命名约定

在Tinder,我们为自定义头部使用了一种结构化的命名规则,即在前面加上X-Tinder-。这样做能减少与未来标准化头部发生冲突的风险,并确保我们的API保持一致性。

使用自定义头部的原因
  1. 避免冲突:通过使用如 X-Tinder- 这样的唯一前缀,我们确保自定义头部与标准头部或其他应用程序和框架使用的头部之间不会发生冲突。
  2. 自我文档化X-Tinder- 前缀立即表明该头部是自定义的(例如 X-Tinder-),从而使开发人员更容易理解其来源和目的。
  3. 增强功能:自定义头部使Tinder能够实现标准头部不支持的功能或元数据。例如,自定义头部可用于跟踪、自定义认证方法、功能标志或版本控制。

以下是一些自定义头部的示例:

请求跟踪标准

在Tinder,我们通过为每个请求生成一个唯一的标识并将其注入为自定义头来标准化请求追踪。共享仓库中的追踪模块使用映射诊断上下文(Mapped Diagnostic Context,MDC)来填充这些头部。我们的内部日志库接着使用这些MDC变量捕获应用程序特定的日志和追踪ID。

在本博客的第一部分里,我们研究了Tinder在维护其API生态系统时遇到的难题,以及他们在URI设计、HTTP方法和请求头方面的标准。在第二部分,我们将深入探讨Tinder在请求和响应体、状态码,以及用于执行这些标准的工具和方法方面的API标准,确保其所有API的一致。

编者注: 在这篇文章中,我们将探讨Tinder在构建和维护强大且可扩展API时面临的挑战,并强调遵循API标准的必要性。我们将讨论Tinder所遵循的具体API标准,以及我们用于确保这些标准一致应用的工具和方法。

我们将这次探索分为两个部分。在第一部分中,我们将探讨与维护API相关的挑战,重点是URI设计、HTTP方法和请求头方面的标准。在第二部分中,我们将深入研究请求/响应体和状态码方面的标准,以及Tinder用来实施这些标准的工具和方法。无论你是工程师、技术领导还是高管,理解这些原则都将为你在当今快速变化的技术环境中API标准和规范的重要性提供宝贵的见解。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消