4. 数据契约定义语言(DCL):YAML/JSON格式定义契约、字段类型与约束声明、版本号与兼容性策略
数据契约不能只停留在口头约定上。你得把它写下来,而且要写得机器能读懂。
我个人习惯把这种描述语言叫做 DCL——Data Contract Language。说白了,它就是一份结构化的声明文件,告诉上下游:「我的数据长什么样,你得按这个规矩来」。
今天我们就聊聊怎么用 YAML 和 JSON 把契约写清楚。我还会分享一些踩过的坑,比如版本号没管好,差点让整个湖仓管道崩掉的故事。
4.1 为什么选 YAML 和 JSON?
你可能会问:为什么不用 XML?或者直接用数据库 DDL?
嗯,原因其实很简单。YAML 和 JSON 是数据工程师最熟悉的两种格式。它们轻量、可读性强,而且几乎所有编程语言都有现成的解析库。
我在项目中做过对比:
| 特性 | YAML | JSON | XML |
|---|---|---|---|
| 可读性 | 高(缩进结构) | 中(括号多) | 低(标签冗余) |
| 注释支持 | 支持 | 不支持 | 支持 |
| 解析速度 | 中 | 快 | 慢 |
| 工具链成熟度 | 高 | 极高 | 高 |
| 适合场景 | 人工编写契约 | 程序生成/消费 | 遗留系统 |
我个人建议:契约源文件用 YAML,因为它支持注释,方便团队协作时写说明。程序内部流转或存储到元数据中心时,转成 JSON 更高效。
4.2 契约的基本结构
一份完整的数据契约,至少包含三部分:元信息、字段定义、约束声明。
来看一个实际例子。这是我为一个用户画像表写的契约:
# 数据契约:用户画像宽表
contract:
name: "user_profile_v1"
domain: "user"
owner: "data-platform-team"
version: "1.2.0"
schema:
fields:
- name: "user_id"
type: "string"
required: true
description: "用户唯一标识"
constraints:
- pattern: "^U\\d{8}$"
- max_length: 16
- name: "user_name"
type: "string"
required: true
description: "用户昵称"
constraints:
- max_length: 64
- name: "age"
type: "integer"
required: false
description: "用户年龄"
constraints:
- min: 0
- max: 150
- name: "registration_date"
type: "date"
required: true
description: "注册日期"
format: "yyyy-MM-dd"
- name: "tags"
type: "array"
items:
type: "string"
required: false
description: "用户标签列表"
compatibility:
policy: "backward"
breaking_changes:
- "remove_field"
- "rename_field"
- "change_type"
你看,这份契约把字段名、类型、是否必填、约束条件都写清楚了。生产者按这个结构产出数据,消费者按这个结构解析数据,谁也别耍赖。
4.3 字段类型与约束声明
字段类型不能随便写。我见过有人把「整数」写成「string」,结果下游做聚合计算时全崩了。
我们一般支持以下几种基础类型:
- string:字符串,可附加 max_length、pattern(正则)
- integer:整数,可附加 min、max
- float:浮点数,可附加 precision、scale
- boolean:布尔值
- date:日期,需指定 format
- timestamp:时间戳,需指定 format 和 timezone
- array:数组,需声明 items 的类型
- map:键值对,需声明 key 和 value 的类型
核心原则: 约束越严格,数据质量越高,但灵活性越低。我建议在关键字段上收紧约束,在辅助字段上适当放宽。
约束声明这块,我曾经踩过一个坑。有个字段叫 "status",我定义成 string 类型,没加枚举约束。结果上游程序出了 bug,写入了 "sucess"、"faild" 这种拼写错误。下游消费端按 "success"、"failed" 做判断,直接跳过了 30% 的数据。
从那以后,我对枚举字段一律加 enum 约束:
constraints:
- enum: ["active", "inactive", "suspended"]
4.4 版本号与兼容性策略
版本号是数据契约的命根子。没有版本管理,契约就是一纸空文。
我推荐使用 语义化版本:MAJOR.MINOR.PATCH。
| 版本位 | 变更类型 | 示例 |
|---|---|---|
| MAJOR | 不兼容变更 | 删除字段、修改字段类型 |
| MINOR | 向后兼容的新增 | 新增可选字段 |
| PATCH | 内部修正 | 修改描述、调整约束范围 |
兼容性策略有三种常见模式:
- 向后兼容(Backward):新版本可以读取旧版本的数据。这是最常用的策略。
- 向前兼容(Forward):旧版本可以读取新版本的数据。实现难度大,但适合长期运行的系统。
- 完全兼容(Full):双向兼容。理想状态,但维护成本高。
4.5 知识体系总览
下面这张图把 DCL 的核心要素串起来了。你可以把它当作设计契约时的检查清单。
4.6 实战建议
最后,分享几个我在实战中总结的经验:
- 契约文件要纳入版本控制。和代码一起放在 Git 仓库里,每次变更都有记录。
- 自动化校验。CI/CD 流程里加一步契约校验,格式不对直接阻断发布。
- 版本号变更要通知所有消费者。我习惯在契约里加一个
consumers字段,列出所有下游系统,变更时自动发通知。 - 不要过度设计。刚开始用 5-10 个字段就够了,别想着一次定义 100 个字段。迭代着来。
一句话总结: 数据契约不是写给人看的,是写给系统和团队之间的信任看的。格式清晰、版本可控、约束明确,这三点做到位,湖仓一致性就有了最底层的基础。