本章内容涵盖:
- 在流水线中定义 API 工件源文件的自动化治理控制
- 在流水线中执行治理控制
- 创建和管理多文件 API 定义
大型软件项目通常涉及多个团队为组织构建许多外部 RESTful API。集中式的人工治理是一种确保 API 满足组织的 API 一致性和可用性标准的方法。但正如第1章所述,集中式人工治理难以扩展,导致敏捷性降低、交付周期变长,以及开发者挫败感增加。那么,治理团队如何尽量减少人工设计审查的需求,转而实现一致应用的、自动化的自助式审查,以支持众多开发团队呢?
自动化治理并不是唯一的问题。在软件交付过程中,会从 API 定义文件生成多个 API 工件。API 工件指的是 API 定义文件及其生成的衍生物,供 API 使用者及其他内部利益相关者使用。它们包括 API 参考文档、客户端 SDK、替代的 API 定义格式、API 评分报告等。当开发团队使用手动且临时的流程生成这些工件时,一致性会受到影响。那么,组织如何为团队提供一种一致的方式来生成 API 工件呢?
本章将讨论如何将 API 工件的治理和管理纳入已有的部署流水线中。部署流水线(也称为持续集成/持续交付流水线,CI/CD 流水线)是一组流程,能够以可靠且可复用的方式将软件变更从版本控制提交转化为可发布的软件。我将讨论流水线如何为 API 设计者提供一个集中的、自动化的、快速的反馈通道,用于检测 API 设计一致性、符合性及其他 API 定义评分指标。我还将介绍流水线如何用来生成并存储待发布的 API 工件。
为何要在 CI/CD 流水线中管理 API 工件?在第1章中,我提到 APIOps 使团队拥有更简化的 API 开发工作流、更快的 API 设计审查反馈,以及更一致且合规的 API。将 API 工件纳入 CI/CD 流水线,是帮助团队实现这些好处的核心实践。流水线让组织能够快速高效地发布 API 及其相关工件,并保证符合正确的一致性标准。它提供了一个集中且标准化的方式,自动执行设计方案检查、符合性检测,并从 API 定义中生成工件。当组织的 API 风格指南引入新的设计规范时,可以将其自动化到流水线中,确保每次 API 定义文件变更都经过相同的自动化设计质量检查。这最大限度地减少了人工治理任务的执行,降低了人为错误的可能性和与 API 定义相关的缺陷风险。
我已经概述了在流水线中管理 API 工件的好处。那么这如何契合 APIOps 及整个 API 产品生命周期的主题呢?管理部署流水线中的工件的核心理念是:API 工件是重要的交付物,因此 API 定义和 API 指南文档应被视为代码。特别是,原始 API 定义可能会被发布,以便 API 使用者直接下载和使用,或者用于生成 API 参考文档或客户端 SDK。API 定义的所有用途概述如下:
- 产品设计 — 用于产品规划和设计中的讨论
- 实现 — 用于生成实现代码
- 测试 — 用于测试基础、生成测试代码和模拟 API
- 文档 — 用于生成 API 参考文档
- 工具 — 用于生成服务器存根和客户端 SDK
- API 评分 — 用于生成 API 一致性评分指标
- API 监控 — 用于可观测性和安全工具,以及 API 网关,监控已部署环境中的 API 行为
因此,重要的是将 API 定义视为软件构建过程中的一级输出,并对其应用类似于代码的工程纪律,正如团队对待代码一样。
本章将如何介绍 API 流水线概念:组织在构建和部署软件及 API 工件方面有复杂需求,因此没有“一刀切”的方式来管理 CI/CD 流水线中的 API 工件。我不会推荐某个具体流水线,而是会提出一个 API 工件交付的问题,并展示一个假设的 API 团队如何使用其 CI/CD 流水线治理和管理工件。我的目标是展示该团队如何演进其流水线,而不是给出固定的流水线阶段建议。请记住,你可以使用第2章讨论的 A3 思维方法来解决你所在场景的流水线问题,从而设计适合你的流水线。我还将使用风险与控制表,展示流水线如何在各阶段管理 API 治理风险。
本章假设你对构建服务器(也称为 CI/CD 服务器)有一定了解。构建服务器是自动化部署流水线中所有任务并整合所有组件的平台。我将以流行的构建服务器 GitHub Actions 平台为例,展示团队如何将 API 定义和 API 指南文档纳入流水线。再次强调,我的目标不是教你 GitHub Actions 的全部细节,而是展示一个如何整合的示例。
术语表
以下是本章中使用的一些术语说明:
- API 治理团队 — APIOps 解决的是多团队同时设计 API 的大规模问题,需要一个团队负责 API 治理,帮助开发团队设计和构建 API。小型组织一般不存在这个问题,但大型组织一定会遇到。有些大型组织将此职责交给维护 API 网关的团队,有些则设立专门的跨职能 API 平台团队。本书中,我将负责监督 API 一致性的团队称为 API 治理团队。
- API 设计提案 — 遵循第1章1.5.1节讨论的 API 设计优先方法,开发团队可提交 API 设计提案作为拉取请求(PR),供治理团队审查。API 设计提案指的是拟议的 API 设计变更(对于 RESTful API,通常是 OpenAPI 定义文件)、API 指南文档及治理团队评审所需的其他信息。
- API 参考 — 指渲染后的 API 定义文件。对于基于 REST 的 OpenAPI 设计,根据 OpenAPI 规范(OAS),API 定义是原始的 OpenAPI YAML 或 JSON 文件。API 参考是利用 Swagger UI、Stoplight Elements、Redoc 或开发者门户的 API 参考渲染系统等工具,从 API 定义文件生成的人类可读文档。
- API 指南文档 — 指区别于由 OpenAPI 定义生成的 API 参考的文档。API 指南文档包括开发者门户上的介绍页面,如 API 介绍、认证、错误处理、分页、版本、废弃策略、教程等。在本章中,我将 API 指南文档视为项目仓库中的 Markdown 文件。尽管本书中将 API 文档内容简化分为 API 参考和指南文档,技术写作者实际上会对 API 文档内容做更细致的分类,参见 Jared Bhatti 等人著作《Docs for Developers: An Engineer’s Field Guide to Technical Writing》(Apress, 2021)。
- 代码托管服务 — 指项目源文件、文档及其他文件资产的文件托管平台。它基于版本控制系统(如 Git),维护文件修订历史,还具备缺陷跟踪、基于 Wiki 的项目文档、邮件列表和发布管理等软件项目管理功能。常见例子包括 GitHub、GitLab、Bitbucket 和 Azure DevOps Server。
本章示例项目代码地址为:mng.bz/ngo2 和 mng.bz/v8a4。
8.1 共享 API 设计提案的问题
假设你的组织 Acme 有 10 个团队构建各种外部 API。假设开发团队先设计他们的 API 定义及部分指南文档,并将这些提交给治理团队审查。他们应该如何发送文件呢?
一种方案是通过电子邮件发送文件。Acme 治理团队可以审查变更,并回复邮件告知是否批准,或者建议修改文件的具体行数。当开发团队做出修改后,可以再次发送给治理团队,后者对比文件的前后版本以确认是否符合建议。图8.1展示了这一流程中可能发生的沟通示例。
这种邮件往来很快就会变得复杂且难以跟踪。在这种情况下,如果同时有10个团队进行类似的沟通,管理和追踪这些文件变更及相关讨论会变得非常困难。团队面临的核心问题是如何跟踪跨文件的变更以及围绕这些变更的讨论。
8.2 版本控制来助力
团队之间共享和协作文件变更的常见解决方案是将 API 定义文件存储在版本控制系统中。在这种情况下,团队应将他们的 API 定义文件添加到项目的版本控制仓库中——就像管理其他源代码文件一样。这不仅适用于 API 定义文件,任何与 API 相关的源文件,比如指南文档和 API 一致性测试,也都可以存储在版本控制中。开发团队可以将他们的 API 设计提案以拉取请求(PR)的形式提交给治理团队审查。
在治理团队审查 PR 之前,PR 必须通过自动化检查,以确认其满足最低设计审查标准(Minimum Design Review Criteria,简称 MDRC)。MDRC 是一个质量检查清单,用来验证 API 设计提案是否符合设计审查流程的最低要求。在此情境下,假设 API 治理团队设定了以下 MDRC 标准:
- PR 标题中包含工作任务编号的引用。假设团队使用 Atlassian Jira,工作任务编号格式为 DEV-XXXX,其中 X 是数字。此外,PR 描述需要说明此次变更的原因。我称这些规则为 PR 描述策略,即 PR 标题和描述必须满足的规则,才能合并。
- PR 必须通过所有 API 语法检查(linting checks)。
- PR 必须通过所有重大变更检查(breaking change checks)。
- PR 应通过对 API 指南文档的所有自动化语言检查(prose linting checks),包括标点、拼写、语法和语言风格。
- 在通过 MDRC 并且 PR 合并到主分支之前,还需满足第五条标准——PR 必须经过治理团队成员的审查和批准。
我将以上五条标准统称为合并标准(merge criteria)。
8.3 自动验证 MDRC
在此场景中,对于第一条标准,Acme 团队可以使用脚本在 PR 构建运行时自动验证 PR 标题是否符合要求。团队还可以使用 PR 模板,提醒 PR 提交者填写变更原因,方便审查者理解。
对于第二和第三条标准,团队可以使用 Spectral(第3章讨论过)执行语法检查,使用 Tufin/oasdiff(第4章讨论过)进行重大变更检测,这些检查会在构建过程中运行。运行这些检查的构建任务称为 PR 构建(PR build)。
对于第四条标准,即 Acme 治理团队成员必须审查 PR,团队可以利用代码托管服务的“代码所有者”(code owners)功能,确保在合并 PR 前获得治理团队成员的批准。代码所有者是代码托管平台的功能,允许团队为版本控制仓库中的特定文件、文件类型或目录指定领域专家或专家团队(通常不属于当前拥有团队),这些代码所有者必须批准涉及相关文件的 PR,才能合并。
图8.2 展示了一个自动验证 MDRC 并满足合并标准的流程示意。
在这个场景中,有一点背景需要注意:Acme 团队已经采用了 CI/CD,并且拥有用于构建和部署其软件服务到不同环境的部署流水线。只是他们还没有在这个流水线中管理自己的工件(artifact)。
部署流水线
部署流水线主要包含两个阶段——持续集成(Continuous Integration,简称 CI)和持续交付(Continuous Delivery,简称 CD,前文提到过)。持续集成是一种实践,团队会定期(至少每天一次)将其变更合并到源码仓库的主分支。构建服务器是执行将代码转换成软件工件任务的平台,同时运行测试以验证每次变更后整个软件系统的正常运行。在 CI 阶段,这些测试是面向开发者的技术测试,帮助开发者快速确认没有引入问题。CI 阶段的产出是一个打包好的候选发布版本,可以部署到更高级的环境进行进一步测试。
在持续交付(CD)阶段,候选发布版本会被部署并测试于越来越接近生产环境的环境中(也称为更高级环境),以验证其满足所有验收、性能、安全等标准。向更高级环境的部署是自动化的,可以自动触发或按需触发。实践 CD 的团队会使用功能开关(feature flags)和关键接口(keystone interfaces)来对用户隐藏尚未完全开发的功能。
持续部署(Continuous Deployment)是 CD 的进一步提升,指每个通过流水线所有阶段的提交都会自动部署到生产环境,无需人工干预。持续部署意味着用户能持续看到生产环境中的改进,部署风险较低,但功能开关仍用于隐藏部分未完成的功能。
我在这里使用以下术语来描述部署流水线的各个阶段和组件:
- 源码仓库——用于存放项目代码、测试、配置及基础设施源码文件的版本控制工具。
- 构建——自动化流程,用于编译和打包代码并运行面向开发者的测试(如单元测试和组件测试)。PR 构建(Pull Request build)在 PR 提交时运行,为 PR 创建者快速反馈其变更在技术层面是否有效。主分支构建(mainline build)在 PR 合并到主分支时运行,并将通过的候选发布版本发布到工件仓库。
- 依赖管理系统——管理内部使用的外部库和容器镜像的系统。
- 工件仓库——候选发布版本被发布到工件仓库。工件(artifact)或二进制仓库是以版本管理的方式存储二进制工件及其关联元数据的目录结构。构建工件存储在这里,后续流水线阶段可以将其部署到不同环境。
- API 注册表——运行时系统,允许用户上传和共享 API 定义文件,方便 API 消费者浏览、搜索和查看 API 详情。
- 非生产环境测试——流水线将候选发布版本部署到逐步接近生产的环境,从用户视角测试软件,增强团队对软件准备发布的信心。这些测试包括验收测试、性能测试以及组织认为必要的其他测试。
- 生产环境部署——通过所有非生产环境测试的候选发布版本会被部署到生产环境(或等效环境,如应用商店)。
本书不涉及部署流水线的完整实现细节。关于部署流水线的基础书籍有 David Farley 和 Jez Humble 的《Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation》(Addison-Wesley Professional,2010)。David Farley 的《Continuous Delivery Pipelines: How to Build Better Software Faster》(2021)也提供了很好的流水线入门。Henry van Merode 的《Continuous Integration (CI) and Continuous Delivery (CD): A Practical Guide to Designing and Developing Pipelines》(Apress,2023)则提供了大量关于搭建流水线的实用建议。
假设开发团队使用如图 8.3 所示的部署流水线,将他们的 API 定义文件添加到源码仓库,就可以在流水线中对其进行检查。
我已经描述了在流水线中自动验证 MDRC 的高层次解决方案的样貌。但在让你打开示例项目仓库并用 Visual Studio Code (VS Code) 编辑器通过代码示例来设置本场景下的合并检查之前,先给你介绍一下这个示例所用的构建服务器——GitHub Actions。
注意 如果你已经熟悉 GitHub Actions,可以直接跳过下一节。
8.4 GitHub Actions 简介
GitHub Actions(github.com/features/ac… GitHub 提供的一个工具,用于自动化软件开发生命周期中的工作流,具备完整的 CI/CD 能力。在 GitHub Actions 中,工作流会在仓库发生特定事件时触发,比如创建一个 Pull Request (PR) 或合并到某个分支。事件可以基于一个或多个触发条件启动工作流,并且可以设置只在仓库的特定代码分支上运行。工作流本身是可配置的自动化过程,定义在仓库的 .github/workflows 目录中的 YAML 文件里。除了可以由仓库事件触发外,工作流还可以按计划定时触发,或者手动触发。工作流在一个叫做 runner 的服务器上执行。GitHub 提供了基于云的 runner,支持 Ubuntu Linux、Microsoft Windows 和 macOS。你也可以自建 runner,比如在本地计算机上,GitHub Actions 会连接该 runner 来执行任务。
工作流文件还包含作业(jobs)。一个作业是多个步骤(steps)的集合,这些步骤在同一个 runner 上执行。步骤可以是运行在 runner 上的 shell 脚本,也可以是一个 action。Action 是 GitHub Actions 平台上的自定义应用,用于执行重复性任务。比如,检出代码是 CI/CD 流水线中的常见任务,GitHub 就提供了一个对应的 action:actions/checkout@v3。默认情况下,工作流中的作业之间没有依赖关系,会并行执行。但你也可以配置和修改这种行为。一个作业中的步骤总是顺序执行且相互依赖。
注意 想了解更多 GitHub Actions 平台的内容,可以参考官方文档:docs.github.com/en/actions
为了更好理解这些概念,来看下面的工作流示例。假设 Acme 开发团队有一个 API 服务——Products API,该服务位于你示例项目的 chapter8 文件夹中。它是一个 Java 项目,且已经定义了 GitHub Actions 工作流文件。你可以在项目根目录的 .github/workflows/pr-build.yaml 文件中找到该工作流文件。这个定义在 pr-build.yaml 中的 PR 构建工作流会在针对 main 分支创建 PR 时运行。它在名为 pr-code-build 的作业中包含五个步骤:
- 使用
checkout/v3action 检出源代码。 - 通过
setup-java@v3action 设置用于执行代码的 JDK17 Java 虚拟机(JVM)。 - 使用 Java 构建自动化工具 Maven 编译代码。
- 使用 Maven 运行项目中的单元测试。
图 8.4 展示了如何用 GitHub Actions 模型来表示这些步骤。
以下代码展示了 PR 构建工作流的具体实现。
清单 8.1 PR 构建工作流
name: PR build workflow
on:
pull_request:
branches: [main] #1
workflow_dispatch: #2
jobs:
pr-code-build: #3
name: Build and test source code
runs-on: ubuntu-latest #4
steps:
- name: Checkout out pr branch
uses: actions/checkout@v3 #5
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: maven
- name: Compile
run: |
cd chapter8
mvn --batch-mode clean compile #6
- name: Unit test
run: |
cd chapter8
mvn --batch-mode test
注释说明:
- #1 当 main 分支上有 PR 时触发此工作流
- #2 允许手动触发此工作流
- #3 定义构建代码的作业
- #4 使用 GitHub 提供的 ubuntu-latest runner 镜像执行工作流
- #5 使用 actions/checkout@v3 action 检出代码
- #6 以静默批处理模式运行 Maven 的 clean 和 compile 命令
首先运行这个工作流。如果你还没有操作过,请克隆示例项目,并按照附录 D 中的说明将其设置为私有 GitHub 仓库。然后创建一个 PR 以触发工作流。这个 PR 可以是对任意文件的微小修改,例如,在 README.md 文件末尾添加一行新内容。为此修改创建一个 PR,目标是合并到 main 分支。创建 PR 后,你应该能看到 PR 检查开始执行。通过这个简单的 GitHub Actions 介绍,你已经准备好创建一个工作流来执行 MDRC 检查。
8.5 设置 MDRC
你已经看到 .github/workflows/pr-build.yaml 文件中的 pr-code-build 作业,用于编译和运行单元测试。假设你想在该工作流中新增一个作业,用来执行 MDRC 检查。你会怎么做?
该作业应执行以下四项检查,正如第 8.2 节所述:
- 检查 PR 标题是否包含工作工单编号引用。
- 对 API 定义文件运行 API 语法检查(linting)。
- 对 API 定义文件运行破坏性变更检查。
- 对 API 指南文档运行文本语法检查(prose linting)。
图 8.5 展示了这些检查如何在 GitHub Actions 工作流中进行建模。
现在,按照以下指南在作业中实现 GitHub Action 的步骤。
注意:如果你不想逐步操作这个示例,但想查看完成的代码,可以看看 .github/workflows/pr-build.completed 文件。
8.5.1 检查工作工单引用
首先,编写一个脚本来验证第一个条件——确保每个 PR 的标题中都包含工作工单引用,方便 PR 审查者查找相关工单。该脚本应接受 PR 标题作为输入,并验证标题中是否包含符合格式 [DEV-XXXX] 的工单号,其中 X 是数字。
此外,在工作流文件中创建一个新的作业 pr-api-minimum-review-criteria,在该作业中新建一个步骤,名称为“Check PR title”,调用你的脚本。
具体做法如下:
在你克隆的示例项目中,进入 chapter8 目录,创建一个名为 pr-title.sh 的文件,并使其可执行(在 Linux/Unix 系统上运行命令 chmod +x ./pr-title.sh)。将以下代码添加到该文件中:
#!/usr/bin/env bash
pr_title=$1
pattern="DEV-[0-9]{4}" #1
if [[ ! $pr_title =~ $pattern ]]; then #2
echo "PR title: $pr_title"
echo "Error: pull request title does not contain a
↪ valid Jira issue reference in the format DEV-XXXX."
echo "Please include a Jira issue reference in the title of your PR."
exit 1
fi
注释:
- #1 用正则表达式定义匹配 PR 标题中的工作工单格式
- #2 如果 PR 标题不匹配,打印错误信息并返回非零退出码
提示:如果你不想手动输入代码,可以在 chapter8 文件夹中找到 pr-title.completed 脚本,重命名为 pr-title.sh 即可。
接下来,在 .github/workflows/pr-build.yaml 文件中创建新的作业 pr-api-minimum-review-criteria,将其作为 jobs 节点的子节点添加。该作业应首先将项目代码签出到工作目录的 pr-branch 文件夹(后续运行破坏性变更检查时会用到)。GitHub Actions 上下文对象 ${{ github }} 有一个子属性 ${{ github.event.pull_request.title }},包含 PR 标题,你可以将其传给脚本。
创建一个步骤来运行该脚本,并传入这个属性。以下清单展示了该作业代码,可以追加到现有的 pr-build.yaml 文件中(不是替换):
…
pr-api-minimum-review-criteria:
name: Minimum API design review criteria
runs-on: ubuntu-latest
steps:
- name: Checkout out pr branch
uses: actions/checkout@v3
with:
path: pr-branch
- name: Check PR title
run: ./pr-branch/chapter8/pr-title.sh
↪ "'${{ github.event.pull_request.title }}'"
提示:本章完整的 GitHub Actions 工作流文件可在 .github/workflows/pr-build.completed 找到(路径从项目根目录开始)。
完成以上配置后,创建一个 PR 来测试。为测试效果,可以将 PR 标题设为不包含工单引用,观察作业失败。然后在 GitHub 上编辑 PR 标题,添加一个示例工单引用(如 [DEV-1234]),再对任意文件做个小修改(如空格变动)提交,观察作业重新触发并成功完成。最后合并你的 PR。
注意:这是一个简单示例,脚本也可以扩展,通过调用 Jira 的 REST API 来验证工单号的有效性。
8.5.2 创建 PR 模板
为了满足 MDRC 的第一个条件——PR 应包含有用的描述,建议创建一个 PR 模板,引导 PR 创建者填写必要信息。
在 .github/ 文件夹下创建一个 pull_request_template.md 文件,内容如下:
## Why is this change needed?
## What is the scope of this change?
## Checklist
- [ ] Have you run tests locally?
提示:你也可以从同目录下的 pull_request_template.completed 文件复制内容。
提交代码后,新建 PR 时应该会自动显示该模板。
8.5.3 在流水线中运行 API 语法检查(linting)
接着,为 MDRC 的第二个条件添加自动检查——PR 必须通过所有 API 语法检查。在 pr-api-minimum-review-criteria 作业中添加一条步骤运行 API linting。
ubuntu-latest runner 自带 Node,但没有安装 Spectral CLI。你可以用 npx 命令直接执行 Spectral,无需安装。npx 方便地从 NPM 仓库运行 Node.js 可执行文件。
以下代码演示如何调用 Spectral 运行针对项目中 API 定义文件的 lint 检查,使用 json-api.ruleset.yaml 规则文件。将其添加在“Check PR title”步骤之后:
- name: Run API linting
run: |
npx @stoplight/spectral-cli lint
↪ "./pr-branch/chapter8/apis/product-catalog.oas.yaml"
↪ --ruleset "./pr-branch/chapter8/apis/rulesets/json-api.ruleset.yaml"
提交代码,创建带有有效工单引用的 PR,观察 PR 构建运行。日志输出末尾应显示:
No results with a severity of 'error' found!
提示:示例中是直接在 runner 命令行执行 lint,另外也可以使用 Spectral 官方的 GitHub Action spectral-action。
8.5.4 在流水线中运行破坏性变更检查
针对 MDRC 的第三个条件——PR 必须通过所有破坏性变更检查,继续在 pr-api-minimum-review-criteria 作业中新增步骤。
该步骤使用 oasdiff/oasdiff-action/check-breaking@main Action(详见 github.com/oasdiff/oas…
但首先需要将主分支代码签出到 runner 的 base 文件夹,方便对比 base 中的 API 定义与 pr-branch 中的版本。示例如下:
- name: Check out main branch
uses: actions/checkout@v3
with:
ref: main
path: base #1
注释:
- #1 将主分支检出到
base目录
然后添加运行破坏性变更检查的步骤:
- name: Breaking change checks
uses: oasdiff/oasdiff-action/check-breaking@main
with:
base: './base/chapter8/apis/product-catalog.oas.yaml'
revision: './pr-branch/chapter8/apis/product-catalog.oas.yaml'
fail-on-diff: true
提交代码,创建 PR 进行验证。
测试方法:修改 chapter8/apis/product-catalog.oas.yaml 文件,将路径 /v1/catalog/categories 改为 /vx/catalog/categories(本质是删除旧路径,新增一个新路径),提交并创建 PR。你应看到 PR 构建失败,日志类似:
error at original_source=./base/chapter8/apis/product-catalog.oas.yaml,
↪ in API GET /v1/catalog/categories api path removed without
↪ deprecation [api-path-removed-without-deprecation].
之后提交新改动撤销该变更。
8.5.5 运行文本语法检查(prose linting)
新增步骤检查 MDRC 的第四个条件——PR 中的 API 指南文档应通过所有标点、拼写和语法的编辑检查。
Vale(vale.sh)是一个开源的文本语法检查-s18dohv17ghekkibg3d49b2xdj9tqofoo6acg5e/) CLI 工具,支持依据编辑风格指南检查文档。
在 pr-api-minimum-review-criteria 作业中使用 Vale,示例如下。
示例项目 chapter8 目录下有 .vale.ini 配置文件,包含 Vale 的默认配置。此配置会下载并使用基于微软写作风格指南(learn.microsoft.com/en-us/style…
.vale.ini 文件配置如下:
StylesPath = ./../.github/styles #1
MinAlertLevel = suggestion #2
Packages = Microsoft #3
[*.md] #4
BasedOnStyles = Vale, Microsoft #5
注释:
- #1 外部风格配置文件存储路径
- #2 Vale 报告的最低严重级别(建议、警告、错误)
- #3 使用微软风格包
- #4 仅应用于 Markdown 文件
- #5 启用的风格规则集
项目的 API 指南文档存放在 chapter8/apis/docs 文件夹。
添加以下步骤,下载、安装并执行 Vale 语法检查:
- name: Prose Linting
run: |
wget -nv
↪ https://github.com/errata-ai/vale/releases/download/v2.15.4/
↪ vale_2.15.4_Linux_64-bit.tar.gz
mkdir bin && tar -xvzf vale_2.15.4_Linux_64-bit.tar.gz -C bin
export PATH=./bin:"$PATH"
cd pr-branch/chapter8
vale sync --config=
↪ 'pr-branch/chapter8/.vale.ini' #1
vale pr-branch/chapter8/apis/docs
↪ --config='pr-branch/chapter8/.vale.ini' #2
注释:
- #1 下载并安装外部风格包配置文件,存放在 styles 目录
- #2 使用
.vale.ini配置,对apis/docs文件夹下所有文档执行检查
提交并创建 PR,触发 pr-build 工作流运行。日志中应打印类似图 8.6 所示的信息。
提示:在这个示例中,你是通过在 runner 的命令行调用 Vale 来运行文本语法检查步骤的。另一种方式是使用 Vale 的 GitHub Actions,你可以在 github.com/errata-ai/v… 找到它。
8.5.6 要求 API 管理团队审批 PR
假设你希望强制所有对 API 定义文件或指南文档的更改,都必须经过 API 管理团队成员的审批。在这个示例中,你需要一位拥有 GitHub 账户的同事作为 API 管理团队的审核人。你的同事扮演代码所有者(code owner)的角色——他们是领域专家,必须审核对指定文件所做的 PR,才能合并。
注意:如果你有其他 GitHub 账号,也可以用作这个示例。
为此,你需要对主干分支(mainline branch)实施以下控制措施:
- 所有更改必须通过 PR 进行。
- 对
chapter8/apis目录下的文件和文件夹的所有更改,都需要你的同事批准。 - 所有 PR 必须运行并通过
pr-code-build和pr-api-minimum-review-criteria两个作业,也就是说,这两个作业的状态必须是成功。
注意:关于状态检查,GitHub Actions 平台的 CI 构建会使用 GitHub REST API 的 commit 状态功能,标记提交状态为错误、失败、待处理或成功。API 还允许添加可选描述,用于对构建做高级概述。这些状态会显示在包含这些提交的 PR 的 GitHub UI 上。详情见 docs.github.com/en/rest/com…
为了执行这些控制措施,请在项目根目录下创建一个 CODEOWNERS 文件,将你的同事设为 chapter8/apis/ 目录的代码所有者。你可以通过重命名 _CODEOWNERS 文件为 CODEOWNERS,然后添加你同事的 GitHub ID 来实现。提交此文件到仓库,创建 PR 并合并到主分支。
注意:此处示例中使用的是单一审核人,但 GitHub 也支持将团队设置为代码所有者。详情见 mng.bz/4JGj。
接下来,将你的同事添加为项目协作者。进入仓库设置,点击“Collaborators and Add People”,搜索你同事的 GitHub 用户 ID,然后添加他们。
现在设置 GitHub 分支保护规则,强制所有更改必须通过 PR,代码所有者必须审批更改,且分支构建的状态检查必须通过,才能将 PR 合并到主分支。操作步骤是:进入你的 GitHub 仓库的“Settings”标签页,点击左侧菜单的“Branches”,然后点击“Add Branch Protection Rule”。在“Branch Name Pattern”字段输入你的主干分支名称 main。勾选“Require Status Checks to Pass before Merging”和“Require Review from Code Owners”。在搜索框中,查找并选择“Minimum API Design Review Criteria”和“Build and Test Source Code”(见图 8.7)。
现在测试你的代码所有者控制是否生效。在 chapter8/apis/ 目录下更新任意一个指南文档的 Markdown 文件。提交你的更改并发起一个 PR。你应该会在 PR 页面看到需要代码所有者审核的提示,如图 8.8 所示。
你已经实现了所有的 MDRC(最小设计审核标准)。这些标准作为你部署流水线当前阶段的控制措施。在下一节中,我将概述这些控制措施在示例中所缓解的风险。
8.6 自动化治理控制
PR 构建执行一个质量门控,确保 API 治理团队只审核通过必要检查的提案。质量门控是一种措施,用于确保部署流水线某个阶段的输入满足特定条件后,才会进入下一阶段。部署流水线的每个阶段都有输入和输出,同时也涉及参与者、动作、风险和控制:
- 风险 —— 由于可避免的漏洞导致损害或负面事件的可能性
- 控制 —— 用于减轻风险的措施
- 参与者 —— 在流水线阶段执行任务的角色
- 动作 —— 参与者在流水线阶段执行的任务
这些方面构成了治理模型的基础,帮助治理团队定义每个阶段的自动化治理控制。
提示:有关此自动化治理模型的更多内容,请参阅 DevOps 企业论坛的《DevOps 自动化治理参考架构:交付流水线资产完整性认证》(IT Revolution,2019)。
请参考表 8.1,该表描述了你所完成示例中 API 设计提案在源码控制 PR 阶段的治理模型。(注:该模型可以更广泛应用于代码仓库中的代码变更,但这里仅限于 API 设计提案。)
| 方面 | 详情 |
|---|---|
| 目标 | 为 API 设计师提供快速反馈,针对客观的 API 风格指南检查 |
| 输入 | API 设计提案:• OpenAPI 定义文件• 草稿 API 指南文档 Markdown 文件• 工作票引用 |
| 输出 | 满足最小设计审核标准的 API 设计提案:• 无错误级别的 API lint 问题• 无破坏性变更• 无错误级别的 API 文档问题 |
| 参与者 | • API 设计师• API 治理团队 |
| 触发 | PR 提交 |
| 动作 | • 提交 PR• 审核、批准并合并 PR |
| 风险与控制 | 风险API 一致性缓解检查 OpenAPI 定义是否符合组织的 API 风格指南。控制PR 构建 API lint 检查代码所有者审批风险破坏性变更缓解检查 API 设计变更是否引入未计划的破坏性变更。控制PR 构建破坏性变更检查风险API 文档难以阅读缓解检查 API 指南文档的标点和语法错误。控制PR 构建文本润色检查风险功能变更缺乏可追踪性缓解提供产品功能和版本信息元数据以供 PR 审核参考。控制PR 描述规范检查 |
这展示了示例项目中应用的控制措施。你可以根据自己的项目调整,有些控制可能不适用,或者你希望增加其他控制。例如,有些团队希望检查提交的 API 定义是否能成功生成客户端 SDK。你可以用这个表格来思考你希望应用的控制。
我在之前章节中讨论过 API lint 和破坏性变更检查,这里不再赘述,但会介绍其他的代码所有者、PR 描述规范和文本润色检查。首先,我们先聊聊 PR 构建。
8.7 PR 构建
PR(Pull Request)方便设计者和审核者协作变更。PR 会通知审核者需要评审的变更,还提供用户友好的界面,便于在变更合并到分支之前进行讨论。该界面是追踪讨论和后续变更的强大工具。通过引用引入变更的 PR,有助于问题溯源。
但在审核者开始评审之前,如何确保所有必须的自动化检查(如 API lint、破坏性变更检查和其他测试)都已执行并通过?当 PR 被提交时,构建服务器可被配置触发任务对其进行验证。团队用这些 PR 任务来检查 PR 分支的代码能否编译、静态分析是否通过、开发者测试是否通过。完成后,PR 构建会在 PR 页面显示构建任务状态,方便提交者和审核者查看。若构建失败,提交者可通过 PR 页面链接查看构建日志,调查失败原因。
PR 构建同样适用于 API 设计提案。API lint 可在 API 定义文件上运行,构建应在出现 ERROR 级 lint 问题时失败。破坏性变更检查会比较新旧版本定义文件,检测破坏性变更,构建应在发现破坏性变更时失败。针对 Markdown 格式的指南文档,构建可根据 lint 报告的严重等级决定是否失败。
如果 PR 构建通过所有检查,API 治理团队就可以开始审核 PR,且确信其满足了最小设计审核标准。但他们仍需审查构建报告,找出构建未导致失败但提出的警告等问题。当然,你也可以自定义脚本做更多检查。
部分代码托管服务和构建服务器可能预集成 PR 构建,例如 GitHub Actions。但有时你需在代码托管平台设置 webhook,让它在 PR 事件发生时通知构建服务器。
对很多开发者来说,使用 PR 和 PR 构建是日常工作方式,因他们一天会提交多个 PR。将此流程用于 API 设计审核符合他们的自然工作习惯,几乎无需额外培训。
8.8 PR 描述规范
PR 描述规范是一套规则,决定了好的 PR 标题和描述应包含哪些内容,帮助审核者理解 PR 内容和提出原因。因为提交者可能不在场提供背景,PR 应包含足够信息,否则审核者需要自行摸索。例如,API 治理团队可能要求 API 设计提案 PR 必须包含工作票引用,说明 API 设计针对的功能。若 PR 标题或描述里有工作票引用,会方便审核者了解详情。但如果引用的工单为空或者信息不足,会拖慢审核进度,审核者得额外查找信息或猜测。
PR 模板有助于提醒提交者填写这些信息。PR 模板是自定义和标准化 PR 描述信息的方式,类似提交者的待办清单。通常配置为代码托管系统中的 Markdown 文件,例如 GitHub 中的 pull_request_template.md。部分系统支持针对不同分支使用不同模板,甚至强制校验模板内容,比如确保勾选框已填写。
虽然 PR 模板能提示提交者提供上下文,但不验证填写内容,因此提交者有时可能忽视。为了鼓励填写,建议模板问题简洁。GitHub 提供了丰富的模板示例,见 mng.bz/X1ma。
提交规范(Commit Policies)
显然,PR 描述规范作用于 PR 级别,但对变更的标题和描述也可在提交(commit)级别进行校验。我称之为“提交规范”,是指对每个新提交自动校验的一组规则。若提交符合规则,则接受;否则拒绝,并通常给出拒绝原因。提交规范可帮助实现源码与相关工作票或用户故事的追踪关联。这样,治理团队可基于 PR 审核设置更轻量级的检查。提交规范在 GitOps 和 APIOps 中非常重要,因为版本控制系统既是事实源,也是项目的中央控制机制。
提交规范可以预提交(pre-commit)执行,阻止错误提交的产生,实现快速失败,避免构建服务器验证 PR 时才发现问题,从而不拖慢提交速度。也可以在服务器端,当提交被推送时执行。在 Git 中,提交规范可用客户端或服务器端的 Git hook 实现。Git hook 是一种自定义脚本,在 Git 仓库发生特定事件时自动运行。
例如,定义一个 Git hook,校验提交信息是否以符合格式 XXX-YYYY(X 为大写字母,Y 为数字)的 JIRA 工单号开头。可在项目 Git 仓库的 .git/hooks 目录下创建一个名为 pre-commit 的可执行文件,内容如下:
#!/bin/bash
commit_msg_file=$1
# 读取提交信息
commit_msg=$(cat "$commit_msg_file")
# Jira 工单号匹配模式(DEV-XXXX)
pattern="DEV-[0-9]{4}"
# 校验提交信息是否包含 Jira 工单号
if [[ ! $commit_msg =~ $pattern ]]; then
echo "错误:提交信息中未包含格式为 DEV-XXXX 的有效 Jira 工单号。"
echo "请在提交信息中包含 Jira 工单号。"
exit 1
fi
设置 Git hook 时,建议保证其执行快速、可靠且文档完善,不应在未经提交者同意的情况下修改文件。
客户端 Git hook 的难点在于必须在每个开发者的工作站单独设置,克隆仓库不会自动带来 hook。解决方案有多种,其中之一是使用如开源项目 pre-commit 这类 hook 管理工具,帮助安装和执行 hook。
部分代码托管服务提供丰富的插件或功能
,可以通过 Web UI 更轻松地配置提交规范。例如 Bitbucket 的 Better Commit Policy(mng.bz/y8pd)和 Control Freak(mng.bz/MZrB)插件。
此外,开源 CLI 工具如 commitlint(github.com/conventiona… Git Lint(github.com/bkuhlmann/g…
8.9 代码所有者(Code Owners)
一个外部 API 的 OpenAPI 定义文件可以和 API 服务的源代码放在同一个代码仓库中(如示例项目),也可以放在专门存放 API 定义文件的仓库里。如果负责更新 API 设计的团队对该仓库拥有写权限和 PR 审批权限,他们就可以自行批准 API 设计的 PR,而无需让 API 治理团队参与审核。这可能会带来问题,因为在大型项目中,API 治理团队负责监督整个组织内 API 设计的一致性,是 API 标准的领域专家。如果团队不让 API 治理团队参与审核,设计中可能存在 API lint 和破坏性变更检查未能覆盖的不一致问题,也可能错失 API 治理团队本应给出的重要反馈。那么,如何确保每个 PR 都由治理团队审查呢?
你可以将 API 治理团队指定为 API 定义文件和文档的代码所有者(code owners)。在版本控制仓库中,将特定文件、文件类型或目录指定代码所有者意味着,凡涉及这些文件的 PR,必须经过代码所有者批准才能合并。同时,代码所有者会在有需要他们关注的 PR 提交时收到通知。
在支持代码所有者控制的基于 Git 的代码托管解决方案中,可以在源码仓库根目录添加一个 CODEOWNERS 文件来设置代码所有者。需要注意的是,有些代码托管系统可能需要插件或附加组件才能启用此功能。
CODEOWNERS 是让 API 治理团队参与审核的有效方式,但要注意实施方式。若除了核心更新团队外,过多地要求其他代码所有者审批,会导致 PR 审批延迟,减慢开发节奏。这与 APIOps 降低开发阻力、加速 API 设计与交付的目标相悖。我建议仅针对需要外部治理的具体 API 定义文件设置最小必要的代码所有者。其他变更,最好由源代码变更团队内部审查,因为团队内沟通更高效。
你可以将 API 治理团队设为 API 定义文件的代码所有者,确保作为 API 标准专家的他们,必须先审核并批准 API 定义文件的变更,才能合并到主分支。
GitHub 有设置代码所有者的说明页:mng.bz/4JGj,GitLab 也有相关文档:docs.gitlab.com/ee/user/pro… Software 提供了 Bitbucket 的 Code Owners 插件:mng.bz/aEpm。
注:本书区分“内部 API”(例如团队拥有且供该团队其它微服务使用的微服务 API)和“外部 API”(需要暴露给组织外部,且需多方协作管理的产品型 API,包括产品经理、技术文档、安全团队等)。API 治理团队通常关注后者,尤其是在设计阶段。这时代码所有者文件非常有用,因为 API 治理团队需要审批设计。软件生命周期中的设计阶段,重要的是问:“谁适合审核这次变更?”对于 API 治理团队来说,审核面向外部的 API 设计变更是他们的核心职责。
8.10 文本润色检查(Prose linting)
编写 API 指南文档涉及书写文本内容,并遵循语言使用规范和风格。没有指导,撰写者可能会引入标点、拼写和语法错误,也可能使用多余词汇、行话、陈词滥调和非地道词汇,这会导致 API 指南文档难以阅读。
文本润色检查工具能帮助解决这个问题。它们扫描分析文档,识别语言语法和风格错误,基于权威的风格和用法指导。与普通语法检查不同,文本润色检查支持自定义规则和风格(类似 API lint 工具),使撰写者能根据组织特定的风格、语调和品牌标准检查文本。常用的文本润色工具包括 Vale(vale.sh)、textlint(https://textlint.g…textlint(https-ly2p//textlint.github.io/%EF%BC%89%E5%92%8C) proselint(github.com/amperser/pr… VS Code 插件)。
Vale 是一个流行且速度快的文本润色工具。它的配置文件(默认期望在运行目录找到 .vale.ini 文件)定义语言风格指南,支持 Markdown、AsciiDoc 和 reStructuredText 格式。textlint 是一个用 JavaScript 编写的流行工具,规则以插件形式加载,可校验 Markdown、文本和 HTML 文件中的文本。proselint 是一个基于 Python 的流行工具,旨在精确执行权威作者和编辑的风格指南。
提示:关于文本润色检查的动机,推荐阅读 proselint 作者 Michael D. Pacer 和 Jordan W. Suchow 的文章《Linting Science Prose and the Science of Prose Linting》(mng.bz/gvKv)。关于 Vale 的工作原理,见 Joseph Kato 的介绍文《Introducing Vale, an NLP-Powered Linter for Prose》(mng.bz/eozV)。
遵循 APIOps 原则,尽可能将所有项目源文件存入版本控制,我建议将 API 指南文档写成 Markdown 文件并存放于项目源代码仓库。这样就可以在交付流水线中使用文本润色工具对文档进行检查。作为部署流水线的一部分,可以使用工具将文档上传到开发者门户。开发者门户的 CI 工具如 Readme CLI(github.com/readmeio/rd… Readme 开发者门户。如果你有自定义的开发者门户系统,也可以自行实现上传工具。
作为代码的文档(Docs as Code)
对 API 指南文档运行文本润色检查,体现了将技术文档视为代码的理念——Docs as Code。在《Docs Like Code: Collaborate and Automate to Improve Technical Documentation》(第3版,Just Write Click,2022)一书中,Anne Gentle 阐述了 Docs as Code 方法论:
- 将文档源文件存储在版本控制中。文档应采用轻量、纯文本标记格式,如 Markdown、reStructuredText 和 AsciiDoc。
- 自动从源文件构建文档产物。
- 对文档运行自动化测试。
- 类似代码审查,审查文档变更。
- 以极少甚至无需人工干预的方式发布文档产物。
采用 Docs as Code 方法,文档工作流程和开发团队的工作流程一致。团队可以使用同样的自动化工具链,比如将文档工具集成到部署流水线,文档编辑使用团队代码开发使用的同一 IDE。这带来了潜在的成本优势,因为许多支持该流程的开源工具(例如上文提到的文本润色工具)是免费的。
将技术文档视为代码的主要好处是促进开发团队和技术文档作者在工作方式上的对齐,培养文档的共同所有权文化。开发者通常撰写文档初稿,技术文档作者进行润色和补充。尽量将文档存放在与应用代码相同的仓库中(例如放在 docs 目录),便于团队在开发代码时同步更新文档,也能阻止功能 PR 合并直到文档准备就绪。由于文档是项目源码仓库中的纯文本标记格式,组织中更多人可以参与贡献文档。
Write the Docs 社区联合创始人 Eric Holscher 在 www.writethedocs.org/guide/docs-… 维护了 Docs as Code 方法的入门页面,推荐阅读。
8.11 支持流水线中的 API 设计提案
在增强流水线的源码仓库阶段以处理 API 设计提案时,以下是一些建议供参考。这些实用提示有助于提升流水线的用户体验。
8.11.1 了解用户及其需求
从 API 设计审核的角度看,流水线的主要两类用户是 API 设计师和 API 治理团队。观察他们如何使用流水线及遇到的问题。利用 API 设计审核会议(如第5章所述)识别那些经常发生但尚未自动化的设计审核问题,并将其捕获,纳入流水线需检查的最低设计审核标准。
MDRC(最小设计审核标准)构建报告应针对 API 设计师和 API 治理团队进行优化。也就是说,每次作业运行应生成易于他们阅读的构建报告,内容应包括:
- 执行了哪些 MDRC 规则
- 哪些 lint 规则被忽略
- 报告了哪些 lint 问题但未导致构建失败
我认为应该把 API 治理团队视为流水线报告的一级用户,因此报告的设计应方便他们使用。
8.11.2 设计流水线以实现快速反馈
流水线执行的速度就是特性。API 设计师和审核者希望 PR 构建能快速反馈,因此应优化自动检查,使其运行尽可能快。经验法则是,PR 构建时间不超过 5 分钟。可以考虑使用多个并行执行的作业(如你用 GitHub Actions 做的示例),加快构建速度。
8.11.3 方便查看构建失败原因
构建失败时,API 设计师需要看到一份有用的报告,指明失败原因。CI/CD 服务器的 UI 应该让他们轻松查看失败原因,而不必费力翻日志。Lint 问题应提供足够的上下文信息,指出 API 定义文件中出错的具体区域,并可提供指向 API 风格指南相关章节的链接,帮助用户了解该 lint 规则所对应的规范。用户可通过日志中的链接跳转到风格指南中的对应解释部分。
此外,流水线失败时,应阻止 PR 合并,促使 API 设计师修复问题。
8.11.4 Lint 失败可链接至 API 风格指南
最低设计审核标准应由 API 治理团队定义,若未满足则构建失败。应与团队讨论并参考 API 风格指南,确定哪些 lint 问题等级或流水线检测到的其他问题应导致流水线失败。
Lint 检查应基于 API 风格指南中规定的规范。理想情况下,风格指南应明确哪些规范是强制性的,哪些是可选的,这对设置 lint 规则的问题级别(如 ERROR、WARN 或 HINT)很有指导意义。
在 API 风格指南中使用 RFC 2119 关键词(如 MUST、MUST NOT、SHOULD)有助于确定对应 lint 规则的问题级别。一个优秀示例是 Zalando 的 RESTful API 和事件指南,见 opensource.zalando.com/restful-api…
8.11.5 收集设计审核时间指标
从 PR 提交到 PR 合并所用的时间即为设计审核前置时间。对于那些设计审核耗时过长的组织来说,这是一个重要的指标,需收集并跟踪。有些代码托管服务提供插件,可以跟踪并可视化这些数据,或者你也可以编写脚本采集并展示这些信息。随着流程优化,这个指标应逐渐降低。
8.11.6 在 PR 构建中加入静态安全检查
API 安全关乎保护 API 传输的信息及降低 API 漏洞风险。API 安全涵盖整个 API 产品生命周期,更多内容见第10章。这里重点讲设计阶段检查 API 安全漏洞,即所谓的 API 静态安全测试。该测试确保在设计阶段发现并修复安全漏洞,通常通过针对 API 定义文件运行安全导向的 API lint 规则实现。你可以将这些静态安全检查工具集成到 PR 构建中的 MDRC 阶段,帮助 API 设计师及早考虑 API 安全。
许多 API 安全厂商提供商业安全平台,可集成到 API 产品生命周期的各阶段保护 API。例如:
- 42Crunch(42crunch.com)/)
- Noname Security(nonamesecurity.com)/)
- Salt Security(salt.security)/)
- Traceable(www.traceable.ai)/)
你可以集成这些平台,在部署流水线中扫描 API 定义的安全漏洞。这些工具还会检查认证、授权、传输和数据负载等最佳实践的遵守情况。比如,42Crunch 提供了一个 GitHub Action(mng.bz/670A),可加入基于 GitHub Actions 的流水线。它会对每个 API 定义打分,分数范围从 0(不安全)到 100(无检测问题),你可设置最低通过分数作为构建门槛。每次运行都会生成指向 42Crunch 平台详细安全报告的链接。此类 API 安全平台为流水线提供安全质量闸门。
8.12 渲染 API 定义以便审查
回到你之前操作的示例场景。通过实施了相应的控制,Acme API 治理团队确保他们需要审查的 API 设计提案符合最低设计审核标准。但治理团队遇到了一个新问题。过去,当 API 定义文件通过 Slack 发送给他们时,团队会下载文件,用 VS Code 文本编辑器打开,并通过 VS Code OpenAPI 插件中的 Swagger UI 视图渲染为 API 参考文档。而现在,API 定义文件作为 PR(合并请求)的一部分,他们必须检出 PR 分支,加载文件到 VS Code 中查看。这样虽然可行,但有没有更简单的方法让 API 治理团队在审核时,无需检出 PR 分支就能查看并渲染 API 定义文件为 API 参考?
在设计和设计审核阶段,渲染 API 定义是必要的工作。Acme 团队可以通过以下几种方式解决这一问题,下面小节会进一步讨论:
- 将包含 API 定义文件的版本控制源代码仓库与 API 设计协作工具(ADC)集成,让团队可以在 ADC 中查看 API 参考。
- 在 PR 构建阶段,将 OpenAPI 定义文件发布到服务器,并在服务器上查看 API 参考。
- 使用源代码仓库插件,直接在代码仓库中渲染 OpenAPI 定义文件为可视化 API 参考。
8.12.1 将源代码仓库与 ADC 集成
基于 Web 的 ADC 工具如 Stoplight Studio(stoplight.io)、Apicurioapicurio-ue3j/) Studio(www.apicur.io/studio)、Swa…swagger.io/tools/swagg… Postman(www.postman.com)都提供了-ts5fi1du06eos9e/) UI 界面,可协同定义和编辑 OpenAPI 定义文件。这些工具有许多便捷功能,尤其适合设计和审查 API 定义文件,因为它们允许设计师编辑 API 定义源文件,在定义源文件视图与 API 参考视图间切换,并能添加审查评论(这对 API 审查非常重要)。
注意:很多开发者传统上使用 Postman 发起 API 请求,我有时会被问及 Postman 是否支持 API 设计功能。答案是支持——Postman 支持创建、导入和编辑 OpenAPI 定义,并可以连接 Git 版本控制系统来同步和管理 API 定义的变更。更多关于 Postman API 设计功能可见 mng.bz/oenp。
选择 ADC 时,一个关键特性是应支持使用版本控制系统(VCS)作为 API 定义文件的主要存储库,也就是说,ADC 能够从 VCS 读取和写入文件。
使用 ADC 作为 API 定义的主要存储库
我见过一些团队不是用 VCS 作为 API 定义文件的主要存储,而是直接用 ADC 作为主要存储,所有的设计编辑和审核都在 ADC 中进行。这看似方便,因为部分 ADC 具备 API 注册功能,即除了存储 API 定义文件外,还提供 API 以支持 API 定义的搜索、检索和更新(第9章会更详细讨论 API 注册)。这种模式下,ADC 充当了 API 定义文件的源代码仓库。
使用 ADC 作为 API 定义文件源代码仓库的优点是入门门槛低,尤其适合刚开始使用 ADC 的团队。但也存在一些缺点:
- 一些 ADC 版本控制历史、文件差异比较、并发编辑等功能有限,远不及专门的 VCS 功能强大。VCS 是管理源文件版本历史的理想工具,而版本历史是软件项目审计的重要要求。DevOps 的基础实践之一就是将所有项目源代码和配置文件存储在版本控制中。Nicole Forsgren、Jez Humble 和 Gene Kim 在《Accelerate》(2018)中的研究表明,软件交付周期与版本控制和自动化测试高度相关,部署频率也与持续交付和全面版本控制密切相关。利用轻量的基于 PR 的 API 设计审核流程,配合版本控制的 API 定义文件,有助于缩短软件设计和交付的周期。这也是我在第1章1.5.2节中提到的 APIOps 原则之一——将 API 定义文件存储在版本控制中。
- 其次,使用 ADC 作为主要存储库会限制集成选项。Git 等 VCS 及 GitHub、GitLab、Bitbucket 等代码托管服务提供丰富的集成选项,如构建服务器、工单系统、IDE、代码质量检测系统等,使得基于 VCS 存储的 API 定义文件能与更多工具协同工作。
- 第三,使用 ADC 作为主要存储,会让数据(源文件)与编辑工具(ADC)绑定,可能限制开发者或其他人员选择他们偏好的编辑工具。
8.12.2 在服务器上发布 PR 的 OpenAPI 定义文件
PR 构建可以将 OpenAPI 定义文件发布到某个服务器,并通过该服务器进行渲染。服务器可以用 Swagger UI、Redocly、Stoplight Elements 等样式渲染文件。一个有用的开源工具是 Swagger UI Watcher(github.com/moon0326/sw… OpenAPI 定义文件的变更,并在浏览器中的 Swagger UI 实时刷新,提升开发者体验。
8.12.3 使用源代码仓库查看插件
一些代码托管服务支持,或者通过插件支持,直接在源代码仓库中渲染 OpenAPI 定义文件。这意味着审查 API 设计的 PR 时,审核者可以直接查看 PR 页面上的渲染效果。举例来说,Bitbucket 的 OpenAPI Viewer 插件(mng.bz/aEpm)就能实现这一… 浏览器也有支持 OpenAPI 文件渲染的插件,可在 Chrome 网上应用店找到。
8.13 复用 API 对象:多文件 API 定义
涉及多个团队的大型软件项目,最终可能会产生庞大的单体 OpenAPI 文件,有时长达数万行。如果管理不当,这些文件中可能包含大量重复的 OpenAPI 对象,导致更新繁琐且对象复用困难。当多个团队都需要编辑这些文件时,版本控制中出现文件冲突的概率也会增加。由于文件体积庞大,PR 审核者也难以高效审查,一些 OpenAPI 工具(如编辑器、API 网关等)也可能难以处理如此巨大的文件。那么,团队应如何管理这些庞大的 API 定义文件呢?
从 API 设计角度来看,团队可以通过确保 API 边界范围清晰、合理,防止 API 定义文件规模过大。(关于如何以合适粒度定义 API 边界,更多内容请参考 James Higginbotham 的《Principles of Web API Design》(Addison-Wesley Professional,2021)一书。)
而从 API 定义文件的角度,定义文件应创建(或重构为)引用可复用对象。这些引用对象应位于定义文件的 Components Object 中。根据 OAS 3.1 规范,Components Object 用于存放可复用对象,如 schemas(模式)、responses(响应)、parameters(参数)、examples(示例)、requestBodies(请求体)、headers(头信息)、securitySchemas(安全方案)、links(链接)、callbacks(回调)和 path items(路径项)。
这种引用通过 OpenAPI 的引用对象(Reference Object)实现。引用对象包含一个字段 $ref,其值是一个 URI,用来标识被引用的目标对象。对于内部引用(即同一文档内的引用),引用的 URI 只需包含一个井号(#),后跟片段标识符(fragment identifier)。以下代码片段示例展示了这种用法:
/v1/catalog/products/{id}/reviews:
get:
tags:
- Reviews
summary: List reviews
description: List all reviews for a product.
operationId: listProductReviews
parameters:
- $ref: '#/components/parameters/productId'
responses:
'200':
description: OK
headers:
RateLimit-Limit:
$ref: '#/components/headers/rateLimit'
RateLimit-Remaining:
$ref: '#/components/headers/rateLimitRemaining'
RateLimit-Reset:
$ref: '#/components/headers/rateLimitReset'
content:
application/vnd.api+json:
schema:
$ref: '#/components/schemas/ReviewsResponse'
使用对内部组件的引用对象可以有所帮助,但生成的 API 定义文件仍可能非常庞大。为了进一步优化文件体积,可以将对象从 API 定义文件中拆分出来,放入外部的片段文件或子文档中。最终生成的多文件 API 定义文档由一个父文档和多个子文档组成,父文档通过引用指向子文档。引用中的 URI 表示文档的绝对或相对路径,后面跟随一个 JSON 指针,用于定位子文档中的具体对象(见图 8.9)。
8.13.1 拆分 OpenAPI 定义
chapter8/apis/product-catalog-api 目录中包含了一个基于以下规则拆分的 OpenAPI 文件示例:
- 每个父级 API 定义应有自己独立的文件夹。该文件夹下应有两个子文件夹——
paths和components。components文件夹中应为 API 所有的组件类型各建一个子文件夹,例如headers、parameters、schemas、responses和examples。 - 父级 API 定义中的每个路径对象(path object)应引用其在
paths目录中独立文件里的对应路径对象。该文件可以包含多个操作(如 GET、POST、PUT 等)。 - 每个 header 元素应有自己独立的文件,存放于
headers目录中。 - 每个成功响应(2xx)的响应模式(schema)应有自己独立的文件,存放于
responses目录中。 - 每个错误响应对象应有自己独立的文件,也存放于
responses目录中。 - 每个文件都应是一个有效的 OpenAPI 文档。
- 所有引用(references)应同时包含文件路径和 JSON 指针部分。
该拆分后的目录结构示意如图 8.10 所示。
使用这些规则,父级 API 定义文件中的 paths 和 components 对象示例如下:
openapi: 3.0.3
info:
title: Product Catalog API
version: 0.0.1
…
tags:
- name: Categories
description: A category is a class of products
↪ with common characteristics
…
paths:
/v1/catalog/categories:
$ref: 'paths/v1-catalog-categories.yaml#
↪ /paths/~1v1~1catalog~1categories' #1
/v1/catalog/products:
$ref: 'paths/v1-catalog-products.yaml#
↪ /paths/~1v1~1catalog~1products' #2
/v1/catalog/products/{id}:
$ref: 'paths/v1-catalog-products-id.yaml#
↪ /paths/~1v1~1catalog~1products~1{id}'
/v1/catalog/products/{id}/reviews:
$ref: 'paths/v1-catalog-products-id-reviews.yaml#
↪ /paths/~1v1~1catalog~1products~1{id}~1reviews'
components:
securitySchemes:
$ref: 'components/securitySchemes/OAuth2.yaml#
↪ /components/securitySchemes'
security:
- OAuth2: []
#1 引用包括目标对象的文件路径和 JSON 指针
#2 每个路径对象都存在于自己的文件中
被引用的 v1-catalog-categories.yaml 路径对象内容示例如下:
openapi: 3.0.3 #1
info:
title: Acme Product Catalog API
version: 0.0.1
paths:
/v1/catalog/categories:
get:
tags:
- Categories
summary: List all categories
description: List all categories.
operationId: listCategories
parameters: []
responses:
'200':
description: OK
headers:
RateLimit-Limit:
$ref: '../components/headers/rateLimit.yaml#
↪ /components/headers/rateLimit'
RateLimit-Remaining:
$ref: '../components/headers/rateLimitRemaining.yaml#
↪ /components/headers/rateLimitRemaining'
RateLimit-Reset:
$ref: '../components/headers/rateLimitReset.yaml#
↪ /components/headers/rateLimitReset' #2
content:
application/vnd.api+json:
schema:
$ref: '../components/schemas/CategoriesResponse.yaml#
↪ /components/schemas/CategoriesResponse' #2
'401':
$ref: '../components/responses/401Response.yaml#
↪ /components/responses/401Response' #3
'403':
$ref: '../components/responses/403Response.yaml#
↪ /components/responses/403Response'
'406':
$ref: '../components/responses/406Response.yaml#
↪ /components/responses/406Response'
….
#1 这是一个 OpenAPI 文件
#2 引用 headers 目录下文件中的 header 对象
#3 引用 response 目录下文件中的响应对象
OAS 对根文档引用的 JSON/YAML 子文档的结构没有强制要求。然而,将子文档结构化为完全有效的 OAS 文档意味着它们可以使用符合 OAS 的工具进行语法检查、转换以及版本升级。同时,也可以在支持 OpenAPI 的编辑器中编辑并享受完整的代码辅助功能。子文档中的 $.info.version 字段还允许你在需要时单独对片段进行版本控制,而不必影响根文档。
注意,OAS 编辑者 Mike Ralphson 在 Dev 网站(mng.bz/Y74z)上详细介绍了… OpenAPI 定义。
8.13.2 合并多文件文档
如果想将多文件的 API 定义合并为单个文件(例如用于不支持外部引用的工具),可以使用“打包”(bundle)功能。打包会将来自不同文件或 URL 的外部引用拉取进来,整合到 OpenAPI 定义的 components 对象中。
你可以使用 Redocly CLI 或 Swagger CLI(github.com/APIDevTools…
在你的示例项目中,可以尝试如下操作,将 product-catalog-api 文件夹下的定义打包:
cd apis/product-catalog-api
redocly bundle openapi.yaml --output bundled.yaml
生成的 bundled.yaml 文件中,你会看到所有外部引用已整合为内部引用。
提示:我之前提到过用于打包 API 定义文件的命令行工具。如果你需要一个可嵌入的 JavaScript 库来做这件事,可以考虑 JSON Schema $Ref Parser(github.com/APIDevTools…
8.13.3 多文件 API 定义的注意事项
多文件 API 定义文档能使 API 定义更模块化,更易于理解和维护。它也促进了复用和封装,因为单独文件中的可复用组件能被多个父级 API 定义共享。这减少了重复,并有助于保证 API 参考文档的一致性。多文件定义还方便多个团队协同工作,减少合并冲突的可能。
然而,多文件 API 定义也带来一些复杂性。为了避免混淆,多个文件需要通过清晰的命名规范和指导原则来管理。此外,开发者需要投入时间去理解文件结构和不同组件间的关系。虽然 OAS 支持多文件文档,但并非所有工具和框架都支持。因此,务必确认你所使用的 API 工具是否兼容多文件文档,以避免意外的限制。
8.13.4 多文件 API 定义的工具支持
API 设计师可以手工在 API 定义文件中创建可复用组件。但在文本编辑器中编辑 API 定义的 YAML 或 JSON 文件可能比较繁琐。API 设计协作工具如 Stoplight、Apicurio 和 SwaggerHub 支持可复用的 OpenAPI 组件,并能简化这项工作。它们允许你拥有一个可复用 API 组件库,可在 API 定义文件中引用。
如果你有一个较大的 OpenAPI 定义文件,想拆分成多文件 OpenAPI 定义,这可能是一次性任务,也可能是你希望在部署流水线中执行的操作。你可以使用 Redocly CLI 的 split 功能来完成。比如在 chapter8/apis 目录下尝试:
redocly split product-catalog.oas.yaml --outDir out
使用 Redocly CLI 拆分文件的一个优势是,它可以根据可配置的命名规范来命名生成的文件。注意,生成的子文档片段只包含引用对象,是 YAML 或 JSON 文件,而非标准的 OpenAPI 文档。它们可以直接使用,但你可能也想把它们更新成有效的 OpenAPI 文档。
总结
API治理团队可以定义最小设计评审标准(MDRC)——这是一组自动化检查,API设计变更提案必须通过这些检查才能进入PR评审流程。这些自动化检查应包含API代码规范检查(linting)、破坏性变更检测以及治理团队认为重要的其他自动化检查。
使用PR模板,提示API设计者提供变更原因的上下文信息,有助于PR评审者理解变更的原因和范围,从而加快评审速度。
为文件指定代码所有者(code owners)是一种要求领域专家对团队源码库中的文件变更进行审查和批准的技术。可以将API治理团队成员设置为API定义文件的代码所有者,确保所有设计变更都经过他们的审核。
像Vale这样的文本校对工具可以集成到PR构建流程中,对API指导文档进行标点、拼写、语法、风格和可读性检查。
在使用API设计协作工具时,应确保版本控制的源码库是API定义文件的主要存储库,并将协作工具与其集成。
将API定义文件拆分成多个文件,有助于提高文件的可管理性,增强多团队间的协作,也促进共享API组件的复用。