Schema 定义入门:三种主流方案
聊数据契约,绕不开一个基础问题——Schema 到底怎么写?
我见过不少团队,上来就选 Protobuf,结果业务逻辑还没理清,先被编译流程折腾得够呛。也见过用 JSON Schema 的,文档写得比代码还长,最后没人维护。
说白了,选哪种 Schema 语言,取决于你的场景。这一章,我带你把 Avro、JSON Schema、Protobuf 三种方案过一遍。不搞虚的,直接看代码、看差异、看坑。
核心观点:Schema 不是文档,是契约的骨架。选对格式,演化才可能平滑。
Avro Schema:Kafka 生态的标配
Avro 是 Apache 旗下的序列化框架。它的 Schema 用 JSON 写,这一点很讨喜——你不需要学新的 DSL。
我个人习惯把 Avro 用在 Kafka 消息上。为什么?因为它和 Schema Registry 配合得天衣无缝。生产者发消息时带上 Schema ID,消费者自动拉取,演化规则内置。
看一个简单的例子:
{
"type": "record",
"name": "User",
"namespace": "com.example",
"fields": [
{"name": "id", "type": "int"},
{"name": "name", "type": "string"},
{"name": "email", "type": ["null", "string"], "default": null}
]
}
注意 email 字段。我用了联合类型 ["null", "string"],还给了默认值 null。这是 Avro 演化的关键——新增字段必须有默认值,否则老数据读不了新 Schema。
小技巧:Avro 的 default 不只是给个值,它还决定了演化方向。有默认值的字段可以安全添加,删除字段则要谨慎——老数据可能还依赖它。
JSON Schema:API 契约的瑞士军刀
JSON Schema 不负责序列化,它只做一件事——描述数据长什么样。你想想看,REST API 的请求体、配置文件、事件通知……只要数据是 JSON,就能用 JSON Schema 约束。
我在项目中遇到过最典型的场景:前端和后端各写一套校验逻辑,结果对不上。后来统一用 JSON Schema,前端用 ajv,后端用 jsonschema 库,问题一次性解决。
来个例子:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"userId": {
"type": "integer",
"minimum": 1
},
"userName": {
"type": "string",
"maxLength": 50
},
"email": {
"type": "string",
"format": "email"
}
},
"required": ["userId", "userName"]
}
这里有个坑——required 数组。新增字段时,如果把它放进 required,老客户端直接报错。我建议新字段先不加 required,等所有消费者都升级了再补上。
注意:JSON Schema 本身不提供演化机制。字段增减、类型变更全靠人工协调。所以它更适合内部 API 或文档生成,不适合高频演化的消息系统。
Protobuf:微服务通信的工业标准
Protobuf 是 Google 的序列化协议。它的 Schema 写在 .proto 文件里,然后通过编译器生成各语言的代码。强类型、二进制编码、性能好——微服务 RPC 场景几乎绕不开它。
我记得第一次用 Protobuf 时,最不习惯的是字段编号。每个字段都得手动标一个数字,而且不能重复、不能改。后来才明白,编号才是 Protobuf 演化的核心。
看代码:
syntax = "proto3";
package com.example;
message User {
int32 id = 1;
string name = 2;
string email = 3;
// 新增字段,编号 4
string phone = 4;
}
演化规则很简单:
- 新增字段用新编号,老客户端忽略它
- 废弃字段用
reserved保留编号,防止误用 - 永远不要改已有字段的类型和编号
关键点:Protobuf 的 reserved 关键字很多人忽略。我曾经接手过一个项目,有人删了字段没保留编号,后来新加字段用了同一个编号,结果线上数据解析全乱套。教训深刻。
三种方案怎么选?
直接给结论,不绕弯子:
| 维度 | Avro | JSON Schema | Protobuf |
|---|---|---|---|
| 定义格式 | JSON | JSON | .proto 文件 |
| 序列化 | 二进制 | 无(纯描述) | 二进制 |
| 代码生成 | 可选 | 无 | 强制 |
| 演化支持 | 强(内置兼容规则) | 弱(人工管理) | 中(编号驱动) |
| 典型场景 | Kafka 消息、数据管道 | API 校验、配置文件 | gRPC、微服务 RPC |
选型建议:
- 如果你的数据流经过 Kafka,优先 Avro。Schema Registry 的兼容性检查能省掉你大量排错时间。
- 如果只是 REST API 的请求体校验,JSON Schema 就够了。别为了用 Protobuf 而引入编译流程。
- 如果是微服务之间的 RPC 调用,Protobuf 是默认选项。性能好、工具链成熟、跨语言支持完善。
我的经验:同一个项目里,三种 Schema 可以共存。Kafka 消息用 Avro,对外 API 用 JSON Schema,内部服务用 Protobuf。关键是统一管理好 Schema 文件,别让它们散落在各个仓库里。