API与事件契约治理面试题:OpenAPI、Protobuf与Schema Registry
本页只放标准回答与原理跳转,完整发布流程、Demo和Runbook见契约治理主线。
高频问题
| 问题 | 标准回答 | 原理页 |
|---|---|---|
| 微服务契约包含什么 | 不只有字段类型,还包含状态码、错误、幂等、分页、顺序、版本和业务不变量。 | 契约层次 |
| 为什么滚动发布必须兼容 | 新旧Producer/Consumer会同时在线,MQ旧消息、离线客户端、灾备和回滚会把兼容窗口拉得更长。 | 滚动窗口 |
| POST怎样保证幂等 | 客户端稳定Idempotency-Key,服务端用唯一约束、请求摘要、状态和结果记录复用第一次结果。 | HTTP语义 |
| 为什么Entity不能直接做API DTO | 客户端可能写内部字段,数据库变化会破坏API,新增敏感字段还可能意外泄露。 | 请求响应 |
| 错误模型怎样设计 | HTTP状态表示协议大类,稳定code供程序判断,message面向人,details受版本和敏感边界控制,并定义重试语义。 | 错误模型 |
| Offset和Cursor分页区别 | Offset简单但数据变化会重复/遗漏且深页慢;Cursor按稳定唯一排序位置继续,更适合大表和动态数据。 | 分页 |
| 哪些API变更属于破坏性变更 | 请求字段从可选改必填、删除响应字段、字段类型变化、金额单位变化、错误码语义变化、排序规则变化和字段含义变化都可能破坏旧消费者。最危险的是结构看似兼容但业务语义不兼容,例如金额从元改分。 | 破坏性变更分类 |
| 发布前为什么要测四种版本组合 | 滚动发布和回滚窗口中会同时存在旧Provider、新Provider、旧Consumer、新Consumer。必须验证旧Provider+新Consumer、新Provider+旧Consumer等组合,否则先发新Writer可能让旧Reader遇到未知字段、未知枚举或新单位后失败。 | 多版本兼容矩阵 |
| API版本放URI、Header还是Media Type | URI直观好治理但容易版本膨胀;Header保持URI稳定但网关、缓存和文档要治理;Media Type语义严谨但成本高;服务发现metadata适合内部灰度但不能替代字段兼容。版本号只是识别和路由手段,不能跳过兼容窗口。 | 版本策略 |
| 删除字段前怎么确认没人用 | 不能靠口头确认。要结合网关日志、Consumer Contract、SDK版本上报、字段级埋点、代码依赖扫描、MQ消费组清单,并确认旧实例、旧SDK、历史消息、死信、灾备和回滚镜像都不再依赖。 | 消费者清单和删除门禁 |
| Design-first与Code-first区别 | 前者先评审规范再生成实现,治理强;后者从代码生成文档,上手快但容易把偶然实现变契约。 | OpenAPI |
| Backward Compatibility是什么 | 新Reader能读取旧Writer数据;评审必须明确Reader/Writer版本,避免术语方向误解。 | 兼容方向 |
| Protobuf字段为什么不能复用编号 | 编号是wire身份,复用会让旧字节被解释成另一个字段;删除后应reserved。 | Protobuf |
| 新增枚举为何可能破坏旧消费者 | 旧Runtime可能返回未识别值、抛异常或走危险default;必须有UNKNOWN和安全行为。 | 枚举演进 |
| Event Envelope应有什么 | eventId、eventType、eventVersion、occurredAt、producer、aggregateId/version、追踪和data。 | 事件契约 |
| Schema Registry做什么 | 存储Schema版本并在注册时按兼容策略检查,消息可携带Schema ID供Consumer解析。 | Registry |
| Registry能保证业务兼容吗 | 不能。单位、默认值和事件语义变化可能结构兼容但业务破坏,仍需契约和场景测试。 | Registry边界 |
| CDC契约测试是什么 | Consumer发布它依赖的Provider交互,Provider CI用真实实现验证,防止发布破坏实际消费者。 | Consumer Contract |
| Expand–Migrate–Contract是什么 | 先新增并兼容新旧字段,再双读写、回填和迁移消费者,经过回滚/消息保留窗口后才删除旧字段。 | 迁移流程 |
| 为什么镜像回滚不一定可行 | 新版可能已写入旧代码不认识的枚举、消息或数据库格式,旧镜像启动后会失败,需要兼容前滚。 | 发布门禁 |
场景题:事件新增枚举后旧消费者失败
先停止新Writer扩大影响,按Schema ID和Consumer版本定位;保留失败消息,发布能把未知值映射为UNKNOWN且采取安全行为的兼容Reader,再重放。之后在Schema/契约测试中加入新枚举对旧Reader的真实Runtime验证,不能只做结构Diff。
项目回答模板
我们把OpenAPI、Proto和事件Schema作为版本化制品,CI做breaking diff、旧Reader/新Writer与新Reader/旧Writer组合测试。HTTP写接口用Idempotency-Key,错误使用稳定code。事件带eventId、eventVersion和aggregateVersion;Registry负责结构兼容,Consumer Contract验证行为。数据库和消息字段使用Expand–Migrate–Contract,确认旧实例、历史消息和灾备版本退出后才删除。
本章小结
契约面试不能只背“向后兼容”。要明确Reader/Writer方向、滚动窗口、Schema与行为边界,以及怎样通过迁移和发布门禁落地。
