4、数据契约的Schema定义:字段类型、约束条件、元数据标签、数据血缘标注

数据契约长什么样?说白了,它就是一个结构化的描述文件。你得告诉消费方:我给你什么字段、每个字段是什么类型、有什么限制、打上了哪些业务标签、这数据从哪来的。

我见过太多团队,上来就写契约,结果字段类型写错了,约束条件漏了,下游一接就报错。嗯,咱们今天就把这四块掰开揉碎讲清楚。

4.1 字段类型:别让类型成为第一道坎

字段类型是契约最基础的部分。你想想看,如果生产方说“这个字段是字符串”,消费方按整数去解析,那不乱套了吗?

我个人习惯,在契约里明确写出每个字段的物理类型和逻辑类型。物理类型就是数据库里的真实类型,逻辑类型是业务语义上的类型。

核心原则:物理类型必须精确到长度、精度、是否可为空。逻辑类型要说明业务含义,比如“订单金额(元,保留两位小数)”。

举个例子,一个订单表的契约定义:

{
  "fields": [
    {
      "name": "order_id",
      "physical_type": "VARCHAR(64)",
      "logical_type": "订单唯一标识",
      "nullable": false
    },
    {
      "name": "amount",
      "physical_type": "DECIMAL(18,2)",
      "logical_type": "订单金额(元)",
      "nullable": false
    },
    {
      "name": "created_at",
      "physical_type": "TIMESTAMP(3)",
      "logical_type": "订单创建时间(UTC+8)",
      "nullable": false
    }
  ]
}

我在项目中遇到过,有人把金额字段定义成 FLOAT,结果精度丢失,对不上账。后来我们强制要求金额类字段必须用 DECIMAL,并明确精度。这个坑,踩过一次就记住了。

4.2 约束条件:给数据上把锁

光有类型还不够。你得告诉消费方,这个字段有哪些约束。比如:值不能重复、必须在某个范围内、必须符合某种格式。

约束条件分几类:

  • 唯一约束:这个字段的值不能重复,比如订单ID。
  • 范围约束:数值必须在某个区间,比如年龄 0-150。
  • 格式约束:必须符合正则表达式,比如手机号、邮箱。
  • 枚举约束:值只能从指定集合中取,比如状态字段。
  • 引用约束:值必须在另一个表中存在,比如用户ID必须在用户表中存在。

我的建议:约束条件不要写太多,否则生产方维护成本高。只写那些“不满足就会出大问题”的约束。比如金额不能为负、状态值必须在枚举范围内。那些“建议性”的约束,写在备注里就好。

看一个带约束的契约示例:

{
  "fields": [
    {
      "name": "status",
      "type": "VARCHAR(20)",
      "constraints": {
        "enum": ["pending", "paid", "shipped", "cancelled"],
        "description": "订单状态,只允许这四个值"
      }
    },
    {
      "name": "email",
      "type": "VARCHAR(256)",
      "constraints": {
        "pattern": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
        "description": "用户邮箱,必须符合邮箱格式"
      }
    }
  ]
}

4.3 元数据标签:给数据打上业务烙印

元数据标签,说白了就是给字段贴标签。比如这个字段属于哪个业务域、什么敏感级别、什么数据分类。

为什么要做这个?我举个例子。你有一个“用户手机号”字段,如果没有标签,消费方可能直接拿去分析。但如果你打上“PII(个人身份信息)”标签,消费方就知道要脱敏处理。

元数据标签通常包括:

  • 业务域标签:交易域、用户域、商品域
  • 敏感度标签:公开、内部、敏感、高度敏感
  • 数据分类标签:PII、财务数据、行为数据
  • 生命周期标签:热数据、温数据、冷数据

注意:标签不是越多越好。我曾经见过一个团队,一个字段打了十几个标签,结果没人知道哪个标签是权威的。建议每个字段不超过3个核心标签,标签体系要提前统一。

带标签的契约示例:

{
  "fields": [
    {
      "name": "phone",
      "type": "VARCHAR(20)",
      "tags": {
        "business_domain": "user",
        "sensitivity": "highly_sensitive",
        "data_classification": "PII"
      }
    }
  ]
}

4.4 数据血缘标注:让数据可追溯

数据血缘,就是告诉你这个字段从哪来、经过了什么处理、最终去了哪。没有血缘的数据,就像没有来源的货物,你敢用吗?

我个人习惯,在契约里标注两个层面的血缘:

  • 字段级血缘:这个字段是从上游哪个表的哪个字段来的,经过了什么转换。
  • 表级血缘:这个表依赖哪些上游表,又会被哪些下游表使用。

看一个字段级血缘的标注:

{
  "fields": [
    {
      "name": "user_level",
      "type": "VARCHAR(20)",
      "lineage": {
        "source": "ods.user_behavior.user_id",
        "transform": "CASE WHEN total_amount > 10000 THEN 'VIP' ELSE '普通' END",
        "upstream_table": "ods.user_behavior",
        "downstream_tables": ["app.user_profile", "bi.user_analysis"]
      }
    }
  ]
}

避坑指南:我曾经在项目里发现,血缘标注和实际ETL代码对不上。原因是ETL改了,但契约没更新。后来我们强制要求:每次ETL变更,必须同步更新契约中的血缘信息,否则CI/CD不通过。

4.5 知识体系总览

下面这张图,把数据契约Schema的四个核心维度串起来了。你可以看到,字段类型是基础,约束条件是规则,元数据标签是语义,数据血缘是溯源。四者缺一不可。

数据契约Schema知识体系 数据契约Schema 字段类型 约束条件 元数据标签 数据血缘 物理类型 逻辑类型 唯一/范围 格式/枚举 业务域标签 敏感度标签 字段级血缘 表级血缘 核心原则 类型精确 + 约束必要 + 标签统一 + 血缘可溯

4.6 完整契约示例

最后,咱们看一个完整的契约定义。把上面四块内容整合到一起:

{
  "schema_version": "1.0",
  "table": "ods.order_info",
  "description": "订单信息表,每日全量同步",
  "fields": [
    {
      "name": "order_id",
      "physical_type": "VARCHAR(64)",
      "logical_type": "订单唯一标识",
      "nullable": false,
      "constraints": {
        "unique": true,
        "description": "全局唯一订单号"
      },
      "tags": {
        "business_domain": "trade",
        "sensitivity": "internal"
      },
      "lineage": {
        "source": "mysql.order_db.orders.order_id",
        "transform": "直接映射"
      }
    },
    {
      "name": "user_phone",
      "physical_type": "VARCHAR(20)",
      "logical_type": "用户手机号",
      "nullable": false,
      "constraints": {
        "pattern": "^1[3-9]\\d{9}$",
        "description": "中国大陆手机号"
      },
      "tags": {
        "business_domain": "user",
        "sensitivity": "highly_sensitive",
        "data_classification": "PII"
      },
      "lineage": {
        "source": "mysql.user_db.users.phone",
        "transform": "直接映射,下游使用需脱敏"
      }
    }
  ]
}

总结一下:字段类型要精确到长度和精度,约束条件要必要且可验证,元数据标签要统一且克制,数据血缘要真实且及时更新。这四块做好了,你的数据契约才算真正能用。