4、数据契约的存储与检索:基于Git的存储方案、元数据仓库、契约的搜索与发现

数据契约写好了,版本也定了,接下来就是怎么存、怎么找的问题。我见过不少团队,契约写得漂漂亮亮,结果存在共享文件夹里,或者塞在Wiki某个角落。等到要用的时候,谁也找不到最新版本。说白了,存储和检索这件事,决定了数据契约能不能真正用起来。

4.1 基于Git的存储方案

Git是我个人最推荐的契约存储底座。为什么?因为它天然支持版本管理、分支协作、变更审计。你想想看,数据契约本质上就是一组结构化文本文件,Git处理这个再合适不过了。

4.1.1 仓库结构设计

我建议一个团队维护一个独立的契约仓库,不要跟业务代码混在一起。仓库结构可以这样组织:

data-contracts/
├── README.md
├── .gitignore
├── .github/
│   └── workflows/
│       └── validate-contracts.yml
├── contracts/
│   ├── product/
│   │   ├── product_created_v1.yaml
│   │   ├── product_updated_v1.yaml
│   │   └── product_deleted_v1.yaml
│   ├── order/
│   │   ├── order_placed_v1.yaml
│   │   ├── order_placed_v2.yaml
│   │   └── order_shipped_v1.yaml
│   └── user/
│       ├── user_registered_v1.yaml
│       └── user_profile_updated_v1.yaml
├── schemas/
│   ├── common/
│   │   ├── address.yaml
│   │   └── money.yaml
│   └── enums/
│       ├── order_status.yaml
│       └── user_type.yaml
├── templates/
│   └── contract_template.yaml
└── scripts/
    ├── validate.sh
    └── publish.sh

这里有个细节:按领域划分目录,每个契约文件以事件名_版本号.yaml命名。我在项目中遇到过,有人把所有契约堆在一个目录里,三个月后目录里两百多个文件,找起来简直噩梦。

4.1.2 分支策略

Git分支怎么用?我建议参考Trunk-Based Development的简化版:

分支 用途 生命周期
main 已发布、不可变的契约 永久
develop 待评审的变更 短期
feature/xxx 单个契约的新增或修改 几天到一周
release/x.y 版本发布前的冻结检查 按需

嗯,这里要注意:一旦契约合并到main,就不允许再修改。如果发现错误,只能发布新版本。这个规则虽然严格,但能避免很多混乱。

我曾经踩过的坑:有人直接在main上修改契约文件,然后说"我就改个字段名,不影响兼容性"。结果下游消费者没收到通知,线上直接报错。从那以后,我强制要求所有变更必须走PR流程,并且触发自动化兼容性检查。

4.1.3 提交规范

Git提交信息要有固定格式,方便后续检索和审计。我推荐这种:

feat(contract): 新增订单已发货事件 v1

- 事件名称: order_shipped
- 版本: 1.0.0
- 生产者: order-service
- 消费者: notification-service, analytics-service
- 变更说明: 首次创建

BREAKING CHANGE: 无

4.2 元数据仓库

光有Git还不够。Git擅长存文件,但不擅长搜索和关联查询。这时候就需要元数据仓库了。

说白了,元数据仓库就是把契约的元信息提取出来,存到数据库里。这样你就可以问:"哪些契约涉及用户地址字段?"或者"订单服务发布了哪些事件?"

4.2.1 元数据模型

我设计过一套元数据模型,核心表结构大概这样:

表名 字段 说明
contracts id, name, version, domain, producer, status, created_at 契约基本信息
contract_fields id, contract_id, field_path, field_type, required, description 字段级元数据
contract_consumers id, contract_id, consumer_service, consumer_version 消费者信息
contract_dependencies id, contract_id, depends_on_contract_id, dependency_type 契约间依赖关系
contract_changes id, contract_id, change_type, old_version, new_version, commit_hash 变更历史
我的经验:元数据仓库不要存契约全文,只存结构化元信息。全文存在Git里,元数据仓库负责索引和搜索。这样既保留了Git的版本能力,又获得了数据库的查询能力。

4.2.2 同步机制

Git和元数据仓库之间怎么同步?我建议用Git Hooks或者CI/CD Pipeline:

  1. 开发者提交PR到develop分支
  2. CI触发契约格式校验和兼容性检查
  3. PR合并到main后,触发同步任务
  4. 同步任务解析YAML文件,提取元数据
  5. 元数据写入数据库,更新索引

这样做的好处是:元数据仓库始终与main分支保持一致,不会出现脏数据。

4.3 契约的搜索与发现

存储做好了,接下来就是怎么让团队找到需要的契约。我见过最糟糕的情况:新来的同事想订阅某个事件,不知道有没有现成的契约,结果自己又定义了一个重复的。

4.3.1 搜索维度

一个好的搜索系统应该支持以下维度:

  • 按领域搜索:product、order、user等
  • 按事件名称搜索:支持模糊匹配
  • 按字段搜索:比如搜索"address"找到所有包含地址字段的契约
  • 按生产者/消费者搜索:某个服务生产或消费了哪些契约
  • 按版本搜索:查找某个版本范围内的契约
  • 按变更类型搜索:查找近期有breaking change的契约

4.3.2 搜索实现方案

我个人习惯用Elasticsearch来做搜索索引。元数据仓库的数据同步到ES,前端提供一个搜索界面。搜索结果的展示可以这样:

搜索结果示例:

订单已发货事件 (order_shipped)
  版本: v1 (最新), v2 (即将废弃)
  生产者: order-service
  消费者: notification-service, analytics-service
  字段: order_id, shipping_address, tracking_number, estimated_delivery
  最近变更: 2024-03-15 新增 tracking_number 字段 (兼容)

核心原则:搜索结果的展示要包含足够的信息,让用户一眼就能判断这个契约是不是自己需要的。不要只给个文件名。

4.3.3 契约发现机制

除了主动搜索,我还推荐建立被动发现机制:

  • 订阅通知:团队可以订阅某个领域的契约变更通知
  • 定期报告:每周自动生成契约变更报告,推送到团队群
  • API网关集成:在API网关中嵌入契约查询接口,开发者可以直接通过命令行查询
  • IDE插件:开发者在IDE中编写代码时,自动提示可用的数据契约

你想想看,如果开发者写代码的时候,IDE自动提示"这个字段对应的数据契约是xxx",是不是很爽?

4.4 整体架构图

下面这张图展示了数据契约存储与检索的整体架构:

数据契约存储与检索架构 Git 契约仓库 main 分支(已发布) develop 分支(待评审) feature 分支(开发中) release 分支(冻结检查) YAML 契约文件 JSON Schema 定义 校验脚本 CI/CD Pipeline 格式校验 兼容性检查 元数据提取 同步触发 元数据仓库 contracts 表 contract_fields 表 contract_consumers 表 contract_dependencies 表 结构化元信息 变更历史 关联关系 PR合并触发 同步写入 搜索与发现层 Elasticsearch 全文搜索 字段级索引 Web UI 搜索界面 契约详情展示 API 接口 RESTful 查询 IDE 插件集成 通知机制 变更订阅 定期报告 数据同步 消费者 数据工程师 后端开发者 数据治理团队

这张图把整个流程串起来了:Git仓库存原始文件,CI/CD做校验和提取,元数据仓库存结构化信息,搜索层提供发现能力。每一层各司其职,缺一不可。

我的建议:不要一开始就把所有功能都做全。先搞定Git存储和基本的搜索,再逐步加入元数据仓库、IDE插件、通知机制。迭代推进,比一次性搞个大而全的系统要靠谱得多。