数据契约的 Schema 定义:四种主流规范
上一章我们聊了数据契约是什么,说白了就是服务之间对数据格式的「书面约定」。那这个约定怎么写?用什么语法?这就涉及到 Schema 定义了。
我个人习惯把 Schema 理解成「数据的身份证」——它规定了数据长什么样、每个字段是什么类型、哪些必填、哪些可选。今天我们就来拆解四种主流的 Schema 定义方式:OpenAPI、JSON Schema、Protobuf 和 GraphQL Schema。
核心观点:没有「最好」的 Schema 语言,只有「最合适」的场景。选型的关键在于你的团队生态、性能要求和协作方式。
1. OpenAPI / Swagger 规范
OpenAPI 是目前 Web API 领域事实上的标准。它最早叫 Swagger,后来捐给了 OpenAPI Initiative。说白了,它就是一套用 JSON 或 YAML 描述 RESTful 接口的规范。
一个典型的 OpenAPI 数据契约长这样:
openapi: 3.0.3
info:
title: 用户服务契约
version: 1.0.0
paths:
/users/{userId}:
get:
summary: 获取用户信息
parameters:
- name: userId
in: path
required: true
schema:
type: string
responses:
'200':
description: 成功
content:
application/json:
schema:
$ref: '#/components/schemas/User'
components:
schemas:
User:
type: object
required:
- id
- name
- email
properties:
id:
type: string
description: 用户唯一标识
name:
type: string
maxLength: 50
email:
type: string
format: email
age:
type: integer
minimum: 0
maximum: 150
这里有几个关键点:
- paths:定义 API 路径和 HTTP 方法
- components/schemas:复用数据模型
- $ref:引用其他 Schema 片段,避免重复
- required:标记必填字段
我的经验:我在项目中遇到过团队把整个 API 文档写在一个几千行的 YAML 文件里,维护起来简直噩梦。建议用 $ref 拆分到多个文件,按模块组织。比如 user.yaml、order.yaml,主文件只做聚合。
2. JSON Schema
JSON Schema 是 OpenAPI 的底层校验引擎,但它本身可以独立使用。它不关心 HTTP 方法、不关心路径,只关心「数据长什么样」。
举个例子,一个订单数据契约:
{
"$schema": "https://json-schema.org/draft-2020-12/schema",
"$id": "https://api.example.com/schemas/order",
"title": "订单契约",
"type": "object",
"properties": {
"orderId": {
"type": "string",
"pattern": "^ORD-\\d{8}$"
},
"items": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"sku": { "type": "string" },
"quantity": {
"type": "integer",
"minimum": 1
},
"price": {
"type": "number",
"exclusiveMinimum": 0
}
},
"required": ["sku", "quantity", "price"]
}
},
"totalAmount": {
"type": "number",
"minimum": 0
}
},
"required": ["orderId", "items", "totalAmount"]
}
JSON Schema 的优势在于:
- 纯数据校验:不绑定任何传输协议
- 丰富的约束:pattern、minimum、maxLength 等
- 可组合:用
$defs定义可复用的子 Schema - 跨语言:几乎所有语言都有校验库
避坑指南:我曾经在项目中用 JSON Schema 校验 Kafka 消息,结果发现 draft 版本不一致导致校验失败。团队用的 draft-07,但新引入的库只支持 draft-2020-12。所以一定要在契约头部显式声明 $schema 版本。
3. Protobuf 定义
Protobuf 是 Google 推出的序列化协议,在 gRPC 生态中广泛使用。它的 Schema 定义文件以 .proto 结尾,语法比 JSON Schema 更紧凑。
看一个例子:
syntax = "proto3";
package user.v1;
message User {
string user_id = 1;
string name = 2;
string email = 3;
int32 age = 4;
repeated string tags = 5;
enum Status {
STATUS_UNSPECIFIED = 0;
STATUS_ACTIVE = 1;
STATUS_INACTIVE = 2;
}
Status status = 6;
}
message GetUserRequest {
string user_id = 1;
}
service UserService {
rpc GetUser(GetUserRequest) returns (User);
}
Protobuf 的几个核心特性:
| 特性 | 说明 | 我的评价 |
|---|---|---|
| 字段编号 | 每个字段有唯一数字编号(1-15 用 1 字节,16+ 用 2 字节) | 编号一旦发布就不能改,否则破坏兼容性 |
| 强类型 | int32、string、bool、enum 等 | 编译期就能发现类型错误 |
| 向后兼容 | 新增字段用 optional,删除字段用 reserved | 这是 Protobuf 最让我放心的设计 |
| 代码生成 | protoc 编译器生成 Java/Go/Python 等语言的代码 | 省去手写序列化/反序列化代码 |
关键认知:Protobuf 的 Schema 既是契约定义,也是代码生成器的输入。这意味着「契约即代码」——你改了 .proto 文件,重新编译,所有语言的桩代码自动更新。这在微服务间协作时效率极高。
4. GraphQL Schema
GraphQL 的 Schema 定义语言(SDL)是另一种风格。它既描述数据形状,也描述查询入口。
一个典型的 GraphQL Schema:
type User {
id: ID!
name: String!
email: String!
age: Int
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
createdAt: DateTime!
}
type Query {
user(id: ID!): User
users(limit: Int = 10): [User!]!
}
type Mutation {
createUser(name: String!, email: String!): User!
}
GraphQL Schema 的特点:
- 类型系统自带非空约束:
!表示非空,[Post!]!表示数组本身非空且元素非空 - 查询和变更分离:Query 读数据,Mutation 写数据
- 关联关系自然表达:User 和 Post 通过字段直接关联
- 客户端按需取字段:这是 GraphQL 最吸引人的地方
我的建议:如果你团队的前端对数据灵活性要求高,比如移动端需要不同字段组合,GraphQL 是很好的选择。但要注意,GraphQL 的 Schema 变更管理比 OpenAPI 复杂——因为客户端可以自由组合字段,你很难预判哪些字段被使用了。
如何选择?一张表说清楚
| 维度 | OpenAPI | JSON Schema | Protobuf | GraphQL |
|---|---|---|---|---|
| 传输协议 | HTTP/REST | 任意 | gRPC/HTTP | HTTP/WebSocket |
| 序列化格式 | JSON | JSON | 二进制 | JSON |
| 性能 | 中等 | 中等 | 高 | 中等 |
| 学习曲线 | 低 | 低 | 中 | 中 |
| 工具生态 | 最丰富 | 丰富 | 丰富 | 丰富 |
| 变更管理 | 版本号 + 断点 | 手动管理 | 内置兼容规则 | deprecation 机制 |
| 适用场景 | 对外 API、微服务网关 | 数据校验、配置描述 | 内部服务、高吞吐 | BFF、数据聚合 |
重要提醒:不管你选哪种 Schema 语言,数据契约的核心不是语法,而是「约定」本身。我见过团队用 Protobuf 但字段编号随意分配,也见过用 OpenAPI 但 required 字段三天两头变。工具只是载体,契约意识才是根本。