OpenAPI 与 JSON Schema:契约怎样被机器校验和生成
有了 OpenAPI 文件,为什么生成 SDK 和运行时校验仍可能互相矛盾? 这类事故通常不是序列化库突然失效,而是双方只共享了字段形状,没有共享字段身份、业务含义、状态前提和演进规则。OpenAPI 描述 HTTP 接口结构,JSON Schema 描述实例约束;机器可读不等于所有工具解释一致。必须固定 OAS 版本、Schema dialect、引用闭包与生成器版本,并用真实消费者验证兼容。
契约不是一份文档,而是跨时间的承诺
入口文档通过 paths/operations 连接参数、requestBody、responses 与 security,再由 components 和 Reference Object 复用结构。Schema 关键字在指定 dialect 与 vocabulary 下解释;解析器先建立引用图、检测循环和不可达引用,再生成代码、测试或文档。
一次契约调用怎样从制品走到业务状态
契约仓库保存规范源文件、解析后的 bundle、规范化摘要、生成器锁文件和兼容报告。发布门禁同时验证语法、引用闭包、breaking diff、示例、服务实现与至少一条生成客户端用例。
把 OAS 3.0 nullable 直接搬到 3.1/3.2 会产生方言错配;新增 required 字段会让旧请求失效;allOf 被误当继承会导致生成器差异;外部引用未锁定内容会让相同版本制品随网络变化。
身份比名字更重要
字段名、错误 message 和 URL 路径都可以改变展示,却不能随意改变机器身份。HTTP Problem Details 的 type URI、Protobuf field number、事件 eventType、业务 errorCode、OpenAPI operationId 与契约 artifact digest 分别在不同边界承担身份。身份一旦被消费者持久化、生成代码或写入分支逻辑,就进入兼容承诺。
兼容性不是单向的布尔值
兼容矩阵的行是活跃消费者及版本,列是候选契约和生产者行为。矩阵单元格保存结构验证、生成编译、样本回放、错误分支与性能限制结果。只测试最新 SDK 会让旧移动端、离线任务、合作方和延迟消息成为盲区。
结构验证必须建立引用闭包
外部 HTTP 引用不应在每次构建时裸拉取 latest。安全做法是将依赖契约固定到内容摘要或内部制品地址,校验来源、大小、媒体类型和摘要后再解析。OpenAPI 规范也提醒外部资源可能来自不可信域,Markdown/HTML 字段在展示前需要清洗;契约门户不能因为“只是文档”而放弃输入安全。
错误契约必须告诉调用方下一步
可运行模型一:让破坏性变更显形
运行 SchemaCompatibilityDemo.java:
javac --release 17 -Xlint:all -Werror SchemaCompatibilityDemo.java
java SchemaCompatibilityDemo预期输出:
oldRequired=2 newRequired=3 newOptional=1 breakingAddedRequired=1 compatible=false第一个模型把兼容判断压缩为稳定数量关系:新增 required、字段号复用、阻塞消费者、浮点误差或状态机非法转换。它不替代真实解析器,却能让评审者看到“哪些输入导致哪条门禁失败”,并能稳定用于回归。
修改模型输入做反向实验:把 optional 变 required、从 reserved 集合取字段号、新增旧消费者不能识别的事件、让金额经过 double 或移除问题类型。若模型仍报告 publishable,说明门禁只检查了表面结构。
可运行模型二:验证运行时仍然收敛
javac --release 17 -Xlint:all -Werror ReferenceClosureDemo.java
java ReferenceClosureDemo预期输出:
documents=7 references=9 resolved=8 unresolved=1 cycles=1 bounded=true publishable=false第二个模型覆盖引用缺失、deadline 传播、事件重放、presence、消费者契约或回滚制品。结构兼容只是第一道门,运行时还必须在超时、重复、乱序、缺省值和版本并存时保持可判定结果。
验收不应只断言进程退出码。需要核对输出中的失败数、阻塞消费者、最终版本、剩余预算、错误泄漏和 rollbackArtifact。任何非零破坏都应关联 owner、例外期限与迁移计划,不能以“先上线观察”替代证据。
Deadline、幂等和重试也是契约
契约应声明最大请求/响应、流式背压、分页大小和批次上限。生成 SDK 若默认无限读取响应或自动重试非幂等调用,同样属于契约缺陷。性能限制必须进入规范扩展、客户端策略或配套机器可读策略,不能只藏在运维文档。
时间、金额、ID、枚举与 Null 要逐个定义
枚举新增必须假设旧消费者会收到未知值。客户端模型保留 UNKNOWN/UNRECOGNIZED 和原始值,不应在未知值上崩溃或默认映射为某个真实业务状态。服务端在收缩枚举前先观察未知值流量,确认所有生产者完成迁移。
事件契约还要承担历史
同步 API 可以通过部署窗口逐步迁移,事件则可能在日志、重试队列、对象存储和审计归档中保留多年。消费者升级后仍要能重放旧事件;新事件投入生产后,滞后消费者仍可能是旧代码。因此事件兼容矩阵必须覆盖历史语料和最大保留期。
发布门禁要横跨生产者和消费者
例外需要机器可追踪:breakingRule、affectedConsumers、owner、expiry、migrationArtifact 与 rollbackArtifact。到期未关闭则阻断,而不是永久 allowlist。高风险契约还应要求双读或影子流量对比实际响应语义。
安全边界必须写进 Schema 和生成器
契约会进入门户、代码生成器、mock 服务和日志系统,因而本身是不可信输入。外部引用限制协议和域名,解析器设置大小、深度、节点与循环预算;Markdown/HTML 清洗;模板禁用任意代码执行;生成目录防路径穿越。不要在 examples、server URLs 或默认值中写真实令牌和内网地址。
个人数据字段标注分类、用途、日志策略和删除传播。SDK 的 toString、调试拦截器与自动日志默认脱敏;事件契约记录数据保留与擦除关联键。契约制品公开范围也要分级,内部管理接口不应因为生成文档而自动暴露。
观测要能回答哪个版本在破坏谁
发布看板至少显示活跃消费者版本分布、最老受支持版本、候选变更破坏数、SDK 生成/编译结果、provider/consumer 通过率、unknown enum、解析失败和弃用接口流量。没有消费者版本可见性,就无法安全删除旧字段或旧路径。
回滚必须能复现旧制品与旧语义
版本号是沟通工具,不是兼容机制。路径版本、媒体类型版本、RPC 新方法或事件新类型都只建立新身份;迁移、双写、流量观测、消费者支持与终止条件仍需单独设计。能兼容扩展时避免无意义大版本,确实改变语义时也不要用小版本掩盖。
架构决策必须留下可复审证据
OpenAPI 描述 HTTP 接口结构,JSON Schema 描述实例约束;机器可读不等于所有工具解释一致。必须固定 OAS 版本、Schema dialect、引用闭包与生成器版本,并用真实消费者验证兼容。 这一主题最终要守住:同一契约制品在受支持工具链中具有确定解释,破坏性 Schema 变更在发布前被阻断。若只能证明最新生产者与最新客户端的 happy path,就还没有建立可演进契约。
契约源、解析 bundle、digest、生成器版本与测试报告可追溯。明确区分传输、结构、业务、运行和演进五层语义。所有机器身份删除后保留并禁止复用,名称不承担隐式判断。
兼容矩阵覆盖所有活跃消费者、历史样本与双向部署顺序。引用闭包、dialect、循环、外部资源和解析预算经过门禁。错误类型、状态、可重试性和 detail 脱敏具有稳定规则。
deadline、取消、幂等键、分页游标和容量上限进入契约。时间、金额、ID、枚举、Null 与未知值有跨语言语义。SDK 实际生成并编译,Provider 与 Consumer 测试均执行。
expand/migrate/contract、弃用终止条件和回滚制品经过演练。两个 Java 17 模型以严格编译运行,输出与正文逐项一致。
以下一手资料用于核对协议与规范语义,实际采用版本仍需匹配解析器、生成器和语言运行时能力:
OpenAPI Specification 3.2.0。JSON Schema 版本标识 2020-12。JSON Schema Specification
