上个月我接手一个老项目的接口改造,当团队决定采用规范驱动开发时,工具选型的任务落到了我头上。第一天看代码就头皮发麻:Swagger 注解散落在每个 Controller 里,Postman 导出的集合已经和两周前的版本对不上,前端照着旧文档调接口,联调一下午报十个 404。
折腾完这轮改造,我花了三个工作日做选型决策,中间推翻过一次方案,最后定下来的组合很朴素:OpenAPI 3.0.3 + Spectral 规则集 + Prism 本地 Mock + OpenAPI Generator 生成客户端 + Redocly 出文档。今天不打算安利任何"全家桶",就完整复盘一遍真实决策过程:怎么拆需求、怎么列候选、怎么打分、怎么用 POC 验证、落地后踩了哪些坑。如果你也打算引入规范驱动开发,或者正卡在工具选型上,这篇应该能帮你把思路理顺。
1. 项目背景:一场联调事故,逼出来的规范驱动开发
1.1 事故现场:文档与代码各说各话
先说事故。这个老项目有三十多个 REST API,接口数量不算多,但问题很典型:接口定义的唯一"官方来源"是 Swagger 注解,前端同学看的是 Postman 里一份手动维护的集合,测试同学手里还有一份 Excel。三份资料各自为政,数据库一个字段改名,Controller 改了,Swagger 注解忘了同步,Postman 集合没更新,Excel 更是三个月没动过。结果就是前端按旧文档联调,签名对不上,响应结构对不上,状态码对不上,一天能有三成工时耗在联调扯皮上。
这不是某个人的疏忽,而是流程层面缺少一个"单一可信源"。注解只能描述实现,不能约束实现;文档靠人肉同步,早晚会失真。这个事故让我意识到:项目缺的不是更好的文档写法,而是一套以规范为源头、能自动向下游传导的机制。
1.2 什么是规范驱动开发(Spec-Driven Development)
规范驱动开发,英文叫 Specification-Driven Development,核心就一句话:先写规范,再谈实现。具体到 API 领域,就是先把 OpenAPI / AsyncAPI 这类机器可读的接口描述文件当成"合同",后端按合同实现,前端按合同对接,测试按合同验证,文档按合同生成。
用装修类比一下:以前的做法像一边装修一边画图纸,墙砌完了才发现插座位置画错了;规范驱动则要求先出施工图,水电、木工、油漆都照着图纸干,图纸改了先通知所有人。迁移到软件开发里,这个"施工图"就是一个 YAML 文件,它同时是文档、是契约、是 Mock 的数据源、是客户端代码的输入。选型要解决的,就是围绕这个 YAML 文件,把编辑、校验、生成、文档、Mock 这几段链路全部打通。
2. 选型前的需求拆解:先回答"工具到底要解决谁的什么问题"
2.1 三类用户,三种诉求
选工具最容易犯的错,是上来就比功能清单,把 GitHub Star 数当唯一指标,忘了工具是给人用的。我做的第一件事,是把会用到规范驱动开发的同事分成三类,逐个访谈。
后端开发(3 人):诉求是"照着合同写实现不跑偏",最需要的是规范校验、服务端骨架代码生成,以及一个能提醒"你改坏了合同"的下游机制。
前端开发(2 人):诉求是"别再给我 Word 版接口文档",最需要的是稳定的客户端代码生成、随时可用的本地 Mock 服务,以及变更发生时能第一时间知道。
测试与交付(2 人):诉求是"接口文档别再逾期",最需要的是自动部署的文档站点、规范变更的审计记录。
问完一圈,我有了一个判断:这个团队最痛的不是"缺文档",而是"文档与代码不同步"。所以这次选型的优先级里,自动校验能力要排在文档美观度前面。
2.2 硬性条件与软性条件
我要求把需求拆成两类:不满足就淘汰的硬条件,和影响体验的软条件。硬条件有四条。
- 规范文件必须是纯文本、可版本控制:团队用 GitLab,规范必须能参与 Code Review,不能锁在某个平台的数据库里。
- 校验工具必须能进 CI:命令行执行后返回非零退出码,这是"门禁"的基础。
- 代码生成必须支持我们的技术栈:后端是 Java Spring Boot,前端是 TypeScript,缺一个都不行。
- 文档必须能私有化部署:项目跑在内网,不能依赖外部公开服务。
软条件包括学习成本低、中文资料多、社区活跃、License 友好。软条件不直接淘汰工具,但在最终打分里会占权重。这一步做完,我心里已经有底了:团队真正需要的是一条"规范文件进入 Git 之后,校验、生成、文档、Mock 全自动"的流水线,而不是某个孤立的编辑器或文档渲染器。
3. 候选工具全景与初步筛选
3.1 先定规范底座:OpenAPI 3.0.3,而不是 3.1
很多人以为选型就是选工具,其实第一层决策是选"规范的版本"。OpenAPI 现在有 3.0 和 3.1 两代,3.1 对齐了 JSON Schema 2020-12,表达能力更强,支持type: ["string", "null"]这类联合类型写法。但现实问题是,主流代码生成器和工具链对 3.1 的支持参差不齐。我当时专门去查过 openapi-generator 的 issue 列表,不少语言模板对 3.1 的处理仍然在追赶。对生产项目来说,成熟度优先于新特性,所以我直接锁定 OpenAPI 3.0.3。
顺带说明一个判断依据:项目当时没有任何异步接口,全是同步 HTTP API,所以 AsyncAPI 不在候选范围。如果哪天要上消息队列,再补一个 AsyncAPI 规范文件即可,两者并不冲突。选型要克制,不能把团队暂时不需要的东西也装进来。
3.2 编辑器与校验工具:从 Swagger Editor 到 Spectral
编辑器层面,我盘了四个候选:Swagger Editor、Stoplight Studio、VS Code 的 OpenAPI 插件、以及纯手写 YAML。Swagger Editor 是网页版,适合单文件演示,但项目规范文件拆成模块后,网页编辑体验很差;Stoplight Studio 有可视化表单,对新手友好,但桌面端比较重,而且它的校验能力最终还是依赖 Spectral;VS Code 插件天生贴近开发者的日常工作流。到这里我已有倾向,但先不急着下结论。
真正让我确定路线的是校验工具。Spectral 是 Stoplight 团队开源的规则检查器,把 OpenAPI 文件当作输入,按一套可自定义的规则集输出警告和错误,提供命令行和 API 两种使用方式。规则集本身也是 YAML 文件,可以放进 Git 仓库参与版本管理。这意味着"团队规范"可以被机器强制执行,而不是靠 Code Review 时人肉提醒。
3.3 代码生成工具:openapi-generator 是事实标准
代码生成这一层,候选集中在 swagger-codegen 和 openapi-generator 之间。OpenAPI Generator 是从 swagger-codegen 分叉出来的社区项目,维护更活跃,支持的语言和框架列表长得多,模板体系也更灵活。对我们这种"后端 Spring Boot、前端 TypeScript"的组合,它正好能覆盖两侧。
唯一要注意的是它生成的代码风格偏"工程化",模板定制有学习成本。但这是后期调优的事,不构成选型否决项。我在初筛阶段就把 swagger-codegen 划掉了,理由是它的维护节奏明显放缓,新特性基本都跑到 fork 出来的 openapi-generator 那边去了。
3.4 文档与 Mock 工具:ReDoc 系 vs Swagger UI vs Stoplight Elements
文档工具我单独拿出来比,是因为这块最容易让人眼花。Swagger UI 最经典,几乎所有 Swagger 生态都能零配置集成,但样式老旧,导航效率一般,多文件拆分的规范渲染起来也不是很顺。ReDoc 是单页文档方案,三栏布局清晰,生成物是纯静态文件,私有化部署非常方便;Redocly CLI 是它维护团队提供的命令行工具,除了渲染文档还能做 lint 和 bundle。Stoplight Elements 则是组件化方案,可以内嵌到公司自己的门户里,灵活但需要前端开发额外投入。
Mock 层相对简单,我重点看了 Stoplight 开源的 Prism。它可以根据 OpenAPI 文件中定义的 example 和响应 schema 自动生成模拟响应,本地一条命令就能跑起来,前端在联调前就能开工。其实 Mock 工具市场上还有 Mockoon 这类独立应用,但既然规范已经落在 OpenAPI 文件里,用和规范同生态的 Prism 集成成本最低。
3.5 初步筛选结果:一份候选名单
经过上面的梳理,我淘汰了 Swagger Editor、swagger-codegen、纯手动文档维护,保留了一套候选组合进入打分阶段。这一轮淘汰的原因统一说一句:它们不是不好,而是要么维护状态堪忧,要么和我们"CI 门禁 + 私有化部署 + 版本控制"的硬条件冲突。初筛本身就是消除噪音的过程,没必要对一个明显不符的选项投入打分精力。留下来的候选有三个层面可以自由组合:编辑层(VS Code 插件 / Stoplight Studio)、校验层(Spectral 是唯一选择)、文档层(Swagger UI / ReDoc / Elements)、生成层(openapi-generator 是唯一选择)。
4. 决策矩阵:打分不是拍脑袋
4.1 五个评分维度,权重按项目痛点定
打分这件事,最怕的就是维度定得又全又虚,最后所有工具都得九十分,等于没分。我定了五个维度,权重完全基于项目实际情况来配。
- 功能匹配度(30%):能不能同时覆盖校验、生成、文档、Mock 四件事,或者能不能无缝组合。
- 自动化与 CI 友好度(25%):命令行能力、退出码、Docker 镜像、配置文件的版本可控性。这是硬条件,权重拉高。
- 社区活跃度与维护状态(20%):看最近一年 release 频率、issue 响应速度、核心维护者背景。
- 学习成本与团队接受度(15%):团队三个后端两个前端,能不能在半天内上手。
- 许可与成本(10%):全部要求开源免费或允许内网商业使用,不接受强制订阅的闭源方案。
这里有一个方法论层面的取舍:不要只对"单个工具"打分,要对"组合方案"打分。规范驱动开发的价值来自链路整体,编辑器再好,校验进不了 CI 也没用;生成器再强,文档不能私有化部署同样白搭。
4.2 候选方案打分对照表
我最终把候选归并成三条组合路径,按五个维度打分,加权总分计算方式就是"每项得分 × 权重"求和。
| 方案组合 | 功能匹配 | CI 友好 | 社区活跃 | 学习成本 | 许可成本 | 加权总分 |
|---|---|---|---|---|---|---|
| VS Code 插件 + Spectral + OpenAPI Generator + ReDoc + Prism | 9 | 9 | 9 | 8 | 10 | 8.95 |
| Stoplight Studio + Spectral + OpenAPI Generator + Elements + Prism | 8 | 7 | 8 | 7 | 6 | 7.4 |
| Swagger Editor + Swagger UI + swagger-codegen(旧生态) | 5 | 3 | 4 | 6 | 8 | 4.75 |
这个表说明了两个结论。旧生态整体落后,无论功能还是维护状态都撑不起新流程。Stoplight 全家桶在功能上并不弱,但 CI 友好度和许可成本拖了后腿:Studio 桌面编辑器本身免费,可团队协作和云端托管属于商业版能力,我们不需要云端,但这一层不确定性在打分时还是扣了分。
4.3 POC 验证:分数再高也要上手跑一遍
打分只是把直觉结构化,真正让我下决心的是 POC。我拿项目里一个真实模块"用户管理"做了验证,耗时一个下午加一个上午:
- 写一份符合模块现状的 OpenAPI 3.0.3 文件,拆成 root、paths、schemas 三个文件;
- 用 Spectral 跑一遍,故意在文件里埋三个错误(缺 summary、响应码错误、字段命名违反规范),确认命令行能正确报错并返回非零退出码;
- 用 OpenAPI Generator 分别生成 TypeScript 客户端和 Spring Boot 的 Controller 接口骨架,确认两端编译通过;
- 用 Redocly CLI 构建文档站点,确认静态文件可以放到内网直接访问;
- 用 Prism 启动 Mock 服务,前端同事拿生成好的客户端发了一个真实请求,确认响应结构跟规范里定义的一致。
POC 的结论很干脆:整条链路在半天内跑通,团队里最资浅的前端同学也可以独立完成"改规范 → 生成客户端 → 本地联调"的操作。分数表解决的是"选哪个",POC 解决的是"能不能落地",两个都要,缺一不可。
5. 最终落地:选型结果与完整工作流
5.1 最终工具组合
这节直接给结论。定下来的工具组合和分工如下:
- 规范底座:OpenAPI 3.0.3,按 root / paths / schemas 拆三个 YAML 文件,放在
spec/目录; - 编辑:VS Code + OpenAPI 官方插件,提供 schema 提示和引用跳转;
- 校验:Spectral + 自定义规则集,规则文件
spectral.yaml入库; - 代码生成:OpenAPI Generator,spring 模板生成服务端骨架,typescript-fetch 模板生成前端客户端;
- Mock:Prism,一条命令启动本地模拟服务;
- 文档:Redocly CLI,构建静态站点部署到内网 Web 服务。
补充一下这个组合背后的思路:每一层只选一个领域里最专注的工具,而不是选一个试图包揽所有事情的"全家桶"。Spectral 专注校验,OpenAPI Generator 专注生成,ReDoc 专注渲染,单个工具在各自领域做到极致,再用 CI 把它们串起来。好处是任何一环出了问题都可以单独替换,不会被绑定死。
5.2 工作流怎么跑起来的
为了让流程可复现,我把它固化成一条命令链,并写进 Makefile。核心流程四步:写规范、本地校验、CI 门禁、生成与部署。
先看校验规则集长什么样,这是团队规范被机器化的关键。spectral.yaml的关键片段:
extends: spectral:oas rules: # 所有 operation 必须有 summary,方便文档自动生成 operation-summary: error # 服务端返回的错误响应必须包含 message 字段 consistent-error-body: given: $.paths[*][*].responses[4XX].content.*.schema then: field: properties.message function: defined # 所有 schema 字段命名统一 camelCase camel-case-properties: error这段配置的含义是:团队规范不再是一页 Word 文档,而是可执行的 YAML。谁在 MR 里犯了规,CI 直接标红,代码就没法合并。我当时还加了一条命名规则,所有路径参数必须用 camelCase,防止前后端对参数语义产生分歧。
CI 门禁我用了 GitLab CI 配置,核心 job 只有三行逻辑,但作用很大:
spec-lint: stage: test script: - npx @stoplight/spectral-cli lint spec/openapi.yaml --ruleset spectral.yaml only: changes: - spec/**/*这段配置的巧妙之处在于only: changes:只有规范文件发生变化时才触发校验,避免每次提交都给全体开发造成无效等待。后端改了 Controller 但没动规范,这个 job 不会跑;后端一旦改了规范,门禁立刻生效。
生成客户端的部分,我放在前端构建流程里,而不是把生成物提交进仓库。命令大致是这样:
npx @openapitools/openapi-generator-cli generate \ -i spec/openapi.yaml \ -g typescript-fetch \ -o frontend/src/api \ --additional-properties=useSingleRequestParameter=true不把生成物提交进仓库,是我踩过坑之后才订的规矩,这个坑在下一节细说。
5.3 落地后的效果
改造第三周,我观察到几个具体变化。前端联调平均耗时从原来的"按天算"变成"按小时算",拿到手的客户端类型就是规范的真实映射,签名错了编译期就暴露。文档站点由 CI 自动部署,新版本发布后两分钟内就能看到最新内容,Excel 文档彻底退休。后端开发在 MR 描述里开始附上"改动影响:spec/paths/user.yaml",这在前三个月是不可想象的。
当然,变化不是自动发生的。工具只是把流程固化下来,真正的转折点是团队在一次复盘里约定了一个硬规矩:规范文件是唯一的接口事实来源,任何人改了实现必须同步改规范,否则 CI 不让过。这个规矩靠 Spectral 的 error 级别规则做托底,才真正执行下去。
6. 踩坑记录与排查思路
6.1 OAS 版本不统一,规则集白配
第一个坑发生在规范文件刚拆分的时候。团队里一位同事按 3.1 的语法写了一个type: ["string", "null"],而我们的底座锁定在 3.0.3。Spectral 的 oas 规则集对此给出了 warning,但没有阻止合并,结果 OpenAPI Generator 直接解析失败,前端构建当场挂掉。
排查起来其实很快,因为报错信息里带了行号。但根因值得记录:版本约束必须写进规则集,光靠口头约定是不行的。我后来加了一条规则,强制所有规范文件的第一行openapi: 3.0.3必须匹配,并且把 Spectral 的 warning 也统一提升为 error 级别的门禁。
6.2 生成代码与手写代码的冲突
第二个坑是生成物管理方式。早期我把生成的 TypeScript 客户端直接提交进了前端仓库,后来接口一改,重新生成时发现:有一处手写的封装逻辑混在了生成目录里,一整套生成命令会把那处手写代码覆盖掉,前端同事的封装功能直接"蒸发"。
解决办法分两层。第一层是物理隔离:生成目录统一叫src/api/generated,在 ESLint 配置里对这个目录关闭检查,所有手写代码一律放到src/api/handwritten。第二层是流程约定:生成目录等同于构建产物,禁止任何人手改,有定制需求要么改 OpenAPI Generator 的模板,要么放在 handwritten 目录里做二次封装。
6.3 规范改了,实现没跟上
第三个坑最具隐蔽性。有一次后端在规范里给一个订单接口新增了discount字段,前端按新契约把页面都写好了,结果联调时后端返回里根本没有这个字段。规范、实现、消费方三方脱节,这不是工具能自动解决的。
我当时的排查思路是逐步收紧。第一步让 CI 在规范变更时自动给前后端 MR 打标签提醒。第二步加一层轻量契约冒烟测试,用一个脚本读取规范里的 example 值,对已部署的测试环境发起请求,校验响应结构是否和规范 schema 一致。这层测试不追求完整覆盖率,但足以在每次发布前暴露"字段缺失"这类最痛的问题。想做得更深,业界还有 Schemathesis 这类基于规范自动生成测试用例的方案,后续可以作为进阶选项。
6.4 文档站点部署的坑
Redocly CLI 整体很稳,但有两个细节容易翻车。第一是相对路径:构建出来的 HTML 默认引用的是绝对路径下的资源,直接放内网子目录会白屏,需要在 redocly.yaml 里显式配置baseUrl。第二是规范文件拆分后,直接渲染 root 文件会漏掉$ref引用的内容,必须先 bundle 再渲染。我在 Makefile 里加了一步redocly bundle的中间命令,把两个坑一次性绕过去。
6.5 规则集微调:从"能用"到"好用"
选型只是开始,真正让这套链路"好用"的是后续对规则集的持续微调。上线第一个月,我把 Spectral 规则从 6 条加到了 14 条,内容也很克制:
- 所有 operation 必须有 tags,方便文档分类;
- 4XX/5XX 响应必须定义,防止前端拿到意外的错误结构;
- 禁止在响应示例里出现数据库主键字段;
- schema 字段命名统一 camelCase;
- 所有响应对象必须显式声明
type: object。
如果把视野放大到整个规范工具链,这一步其实就是在对"主流工具框架"做持续微调与迭代选型:规则集是少数会在项目生命周期里被反复调整的配置资产。微调的原则是"每加一条规则,就问一个反例"。没有反例佐证的规则不加,因为规则是团队规范的强制投影,加多了会变成开发负担,最后被人绕开。这套做法费的时间不多,但直接把规则集的可用性提升了一个档次。
7. 工具选型方法论沉淀:可以复用的决策框架
7.1 一套通用选型决策模板
项目跑顺之后,我把整个决策过程沉淀成了一个六步模板,后来又在内部技术小组里复用过两次,反馈不错。
- 定义问题:选型先问"解决谁的什么问题",输出干系人清单和痛点列表;
- 区分硬条件与软条件:硬条件用于淘汰,软条件用于打分,不要混在一起;
- 列出候选并做初筛:通过资料排查,把明显不满足硬条件的候选划掉;
- 设计加权打分表:维度不超过五个,权重跟项目实际痛点走,而不是跟"行业趋势"走;
- 做 POC 验证:选一个真实模块,验证"能不能进 CI、生成物能不能编译、团队能不能半天上手"三件事;
- 给方案留退出机制:记录每个工具的替代品,避免被单一工具绑架。
这套模板不限于 API 工具链。后来我给内部另一个项目做埋点方案选型、日志采集器选型,都是同一套逻辑跑通的。
7.2 如果让我重来一次,会改什么
复盘还是要诚实的。如果重新走一遍,我会做三处调整。
第一,POC 应该更早做。我前面花了一个多星期调研和打分,其实三天就够,剩下的时间应该在真实代码仓库里跑通链路,很多问题只有手沾到代码才知道。
第二,应该更早拉前端同学参与决策。选型那天前端同事正在赶版本,我只问了需求没让他们来打分,后来前端发现生成的 TypeScript 客户端版本和他们的构建工具链不完全兼容,补了一次升级才解决。干系人访谈代替不了本人参与。
第三,规范文件的拆分粒度值得提前论证。我们一开始拆成 root/paths/schemas 三个文件,后来发现 paths 增长太快,又花了半天改造成按业务模块拆分。这件事在选型时没怎么被讨论,但它直接影响了日常协作的体验。
这次选型带给我最大的体会是:工具选型的结果永远不是"哪个最好",而是"在约束条件下哪个最不坏"。我们选出来的组合,单看任何一环都不是最炫的——Spectral 的名气不如某些商业产品大,ReDoc 的功能不如全家桶丰富,OpenAPI Generator 生成的代码也不总能让人满意。但组合在一起,它刚好满足了团队"CI 门禁、私有化部署、版本化规范"这几条最硬的约束,而且每一环都可以独立替换。
最后再分享一个小技巧:选型文档别只写"选了什么",一定要写"为什么没选什么"。三个月后再翻决策记录,你会发现当初放弃某个工具的大部分理由都还记得清楚,但最关键的 10% 已经模糊了。把淘汰理由写下来,既是给团队一个交代,也是给未来的自己留一份防后悔药。规范驱动开发的路上,工具会变,但"以规范为单一事实来源"这个原则,我建议你无论如何都要守住。