使用 API First 设计创建可靠且易于使用的 API
文章摘要
API-First 设计流程
下一步是什么?
特别感谢
最近,我需要为一位客户创建一个 API 网关。由于我是开发人员出身,API 设计对我来说是事后才考虑的事情。完成用户故事后,我编写了数据库模式,以便可以继续编写 Web 应用程序的代码。API 的开发过程是有机的,一切从当时可行的角度出发。
但正如我在项目中所了解到的,API 是人与人之间的接口(就像系统与系统之间一样)。精心设计 API 并始终坚持这些设计决策,有助于最终用户更快、更轻松地学习 API。此外,最终用户依赖 API 来创建应用程序。对这些 API 进行重大更改意味着最终用户的应用程序将面临宕机。
文章摘要
在这篇文章中,我们将讨论 API-first 思维方式如何鼓励我们将最终用户放在第一位,以便他们可以依赖简单、可靠和一致的 API。
总的来说,这个过程是这样的:
- 第 1 部分 - 使用 API First 设计创建可靠且易于使用的 API(当前文章)
- 从目标开始
- 创建用户故事
- 构建领域模型
- 绘制 API 草图
- 第 2 部分 - 使用 Swagger / OpenAPI 定义 API 契约
- 编写 OpenAPI 定义
- 开始开发
- 处理 API 设计变更
OpenAPI 3.0 定义作为 API 开发者与其最终用户之间的“契约”,从而建立了关于 API 运行方式的单一真实来源。通过这份定义 API 每个端点运行方式的“契约”,我们可以有效防范 API 发生意外的变更。
API-First 设计流程
让我们来看看我们将为餐馆创建的简单忠诚度应用程序的设计过程。
[1] 从目标开始
我们对忠诚度应用程序的目标是让合作餐厅为顾客创建忠诚度卡,并让顾客能够赚取积分并用积分兑换物品。
设定目标为流程的后续步骤奠定了基础。它还能帮助您明确 API 的实际功能。如果您需要开发与此目标不符的功能,那么您应该考虑将其拆分为单独的应用程序。
[2] 创建用户故事
带着这个目标,让我们使用用户故事来详细说明我们的忠诚度应用程序的功能:
- 作为最终用户:
- 我可以在线注册会员卡
- 我可以登录
- 我每次与合作餐厅交易都可以赚取积分
- 我可以在线查看我的积分余额
- 我可以在线查看我的交易历史记录
- 我可以查看我进行的具体交易
- 作为餐厅经理:
- 我可以看到我餐厅的在线卡注册情况
- 我可以为每次注册打印一张卡片
- 我可以将这张卡标记为已认领
- 我可以查看我餐厅会员卡完成的交易
- 我可以使用用户的积分进行交易
- 作为管理员用户:
- 我可以创建餐厅
[3] 构建领域模型
构建领域模型是将用户故事转化为对象的过程。这些对象具有属性和行为。可以将这些类视为类似于面向对象编程 (OOP) 范式中的类。
有了这些对象,让我们思考一下它们之间的关系:
- 用户可以拥有多张卡
- 卡可以进行多笔交易
- 合作伙伴可以发行多张卡(他们可以出售任意数量的会员卡)
- 合作伙伴可以进行多项交易
为了使这些关系可操作化,我们将引入“外键”的概念。有了外键,我们将字段添加{object_name}_id
到关系右侧的对象中。对于“用户可以拥有多张卡片”的关系,我们将字段添加user_id
到卡片对象中。
[4] 绘制 API 草图
准备好领域模型后,就可以开始绘制 API 草图了。千万不要急着马上开始开发 Web 应用。花点时间仔细思考一下要暴露的每个 API 端点以及 API 的资源布局。
绘制 API 草图迫使你在投入大量开发时间之前,先想象 API 的最终样貌。绘制草图后,请将设计图展示给 API 利益相关者,并获取他们的反馈。在这个阶段,由于尚未实现任何代码,因此进行重大更改几乎无需任何成本。
了解 API 端点
要调用 API,请向服务器发送包含以下组件的请求:
- 基本网址:loyalty-app.com
- 路径:/cards
- 路径的构建方式决定了资源的布局方式。卡片资源的另一种布局方式是将其嵌套在用户资源中,例如 /users/10/cards。
- HTTP 方法:GET
- 查询参数:?name=Raphael
- 请求正文:
- 它用于 POST 和其他 HTTP 方法,但不用于 GET。
- 标题
对于我们上面的例子,当你将它们连接在一起时,请求应该是这样的:
GET loyalty-app.com/cards?name=Raphael
API 最佳实践
以下是一些指导原则。大多数建议并非硬性规定。最终,您需要决定哪种方案最适合您的 API。但无论您做出何种决定,都应力求在 API 设计中保持一致。
标准方法
- 标准方法允许 API 在不同的资源之间拥有一组一致的方法。
- 标准方法以 CRUD(创建、读取、更新、删除)为范本。标准方法列表如下。
- 标准方法应该没有副作用。它只做它想做的事情,仅此而已。这样,我们可以在所有标准方法中保持一致的预期。例如,
POST /loyalty-cards
端点创建会员卡时,它不应该同时创建交易。 - 当仅更新对象的某些部分时,最好使用 PATCH 而不是 PUT。
- 如果您不打算实现资源的所有标准方法,您仍然应该为其构建一个端点,但让它返回 HTTP 405(方法不允许)或 HTTP 403(禁止访问)。这样,您的 API 就具有一致性。
自定义方法
- 自定义方法允许你创建具有副作用的端点。其格式如下
/{resource}/{id}:{custom_action}
- 例如,
POST /loyalty-cards/10:claim_card
- 例如,
使用 HTTP 状态代码
- 使用 HTTP 状态代码来传达含义。并非所有错误都是 500 错误,也并非所有成功的操作都应该是 HTTP 200。
- HTTP 200:
- HTTP 201(已创建):创建资源后
- HTTP 204(无内容):删除资源后
- HTTP 200(OK):操作成功时捕获所有信息
- HTTP 400:用户错误 - 用户尝试了错误的事情
- HTTP 403(禁止访问):当您无权访问您正在访问的资源时
- HTTP 404(未找到):
- HTTP 400(错误请求):检查 HTTP 400 代码的完整列表,如果没有,请使用此代码作为全部代码
- HTTP 500:服务器错误 - 服务器出现故障
- HTTP 500:内部服务器错误 - 几乎涵盖所有应用程序错误。
- HTTP 200:
针对长期运行操作的不同方法
- 作为最佳实践,这些准则适用于始终返回时间短于 30 秒的 API 端点。如果您的端点响应时间过长,最终用户将不得不长时间等待,从而导致用户体验不佳。与其使用我们迄今为止介绍的快速响应范例,不如考虑异步处理范例。
以上就是 API 指南的简要概述。如果您想了解更多,我强烈推荐JJ Geewax 的《API 设计模式》一书。
有关标准端点的完整列表,请参阅以下使用卡片资源的列表:
标准 - 收集端点
- 创建 - 发布/卡片
- 阅读全部 - 获取/卡片
标准 - 资源端点
- 阅读一篇 - 获得 /cards/10
- 更新一个(覆盖对象) - PUT /cards/10
- 更新一(覆盖特定方法) - PATCH /cards/10
- 删除一张 - 删除 /cards/10
让我们学以致用
我们的任务现在变成确定我们的 API 资源布局并将每个类的方法转换为 API 端点。
对于 Cards 类,我们选择将其嵌套到用户资源中。当嵌套资源归其父资源所有时,就会创建类似的层级关系。这意味着,如果我们删除用户 #10,我们也会删除与其关联的所有卡片。如果用户 #10 违反了我们的合理使用政策,我们会禁用他的所有卡片,依此类推。
下一步是什么?
现在,我们已经成功创建了 API 设计草图。在此阶段,我建议您将此草图展示给您的 API 利益相关者。获取他们的反馈,并在此基础上进行迭代。
在下一篇文章中,我们将创建一个 Open API 3.0 定义来定义每个 API 端点的详细信息。有了这个定义,我们可以创建一个模拟
特别感谢
特别感谢 Allen,让我的文章更加条理清晰。这篇博文也感谢以下作者,他们让学习 API 变得如此快乐。
- JJ Geewax 的API 设计模式
- Joshua S. Ponelat 和 Lukas L. Rosenstock 撰写的《使用 Swagger 和 OpenAPI 设计 API》
- 《设计和构建出色的 Web API》作者:Mike Amundsen