学习指南: 第12部分. 数据契约:ODCS 和 dbt 契约检查

模块「第12部分. 数据契约:ODCS 和 dbt 契约检查」中第 3 / 5 节课
您正在未登录状态下查看课程。 请登录,以保存进度并参加测试。

主题:第12部分。数据契约:ODCS 与 dbt 契约检查

难度等级:中级

预计学习时间:3-4小时(理论约1.5小时,实践约2小时,案例分析约0.5小时)

先修知识: 熟练掌握 SQL(SELECT、JOIN、GROUP BY、窗口函数)

了解 dbt 基础(模型、物化、sources、refs)

了解数据仓库的构建原则(grain、缓慢变化维度)

了解软件开发生命周期(PR、评审、CI/CD)

基本熟悉 YAML 语法

了解数据质量和数据测试的概念

学习目标: 区分数据契约的两个层级:产品级(ODCS)和模型级(dbt schema.yml),并理解每个层级的职责

识别契约漂移的三种类型(架构漂移、语义漂移、流程漂移),并描述对消费者的影响

按照给定模板,在模型变更时撰写最小化的契约影响说明

将 ODCS 承诺与具体的 dbt 测试、singular 测试和手动评审事实关联起来,评估覆盖率

为 LLM 助手(例如 Qwen Code)设计提示,用于在不修改文件的情况下查找漂移和测试覆盖率的缺口

概述:数据契约是数据仓库的产品承诺与其 SQL 实现之间的可验证层。在 SDD Data 规范的范围内,契约不是装饰性的 YAML 文件,而是关于模式、质量、SLA 和破坏性变更策略的正式协议。本章讨论契约的两个层级:ODCS(Open Data Contract Standard)作为数据产品的技术契约,以及 dbt 模型契约作为具体模型的形式。特别关注契约漂移——即模型代码变更但契约保持不变的情况,这会导致消费者处数据含义的静默偏差。本章研究三种类型的漂移(架构漂移、语义漂移、流程漂移)、其检测方法以及 dbt 测试作为契约证据基础的角色。实践部分围绕 Qwen 查询展开,该查询对比 ODCS 规范、模型描述、schema.yml 和 SQL 代码,生成一份用于 PR 描述的最小化契约影响说明。

核心概念: Odcs(open data contract standard):数据产品技术契约的机器可读描述标准。在课程中以 specs/customer_360_contract.odcs.yaml 文件形式呈现。包含对模式(字段、类型、是否必填)、质量(新鲜度、完整性、唯一性的 SLA)、PII 策略和破坏性变更描述的承诺。这是产品级契约:它回答"数据仓库向外部世界承诺了什么"的问题。

dbt 模型契约:models/schema.yml 中具体 dbt 模型形式的描述:名称、描述、带有类型、测试和文档链接的列。这是模型级契约:它回答"该项目中如何实现这一承诺"的问题。在教学示例中 contract.enforced: true 不被启用,以免在第一次学习中与适配器约束混为一谈;在生产环境中,在物化支持的地方启用它。

两个契约层级及其区分:ODCS 描述产品,schema.yml 和测试描述具体模型。没有哪一层可以替代另一层。如果只保留 dbt 测试,可能会得到一个语义错误的"绿色"模型。如果只保留 ODCS,可能会得到一个没有执行证据的漂亮契约。

架构漂移:契约漂移的第一种类型。当模型中添加、删除或重命名字段,而 ODCS 规范未更新时发生。例如:在 mart_customer_360 中添加了 product_code 列,但 ODCS 仍列出原来的字段集。dbt 可以通过所有测试,但消费者将获得契约中未描述的结构。

语义漂移:第二种漂移类型。字段保留在模式中,但其含义发生变化。例如,risk_event_count_7d 指标开始按不同的时间窗口计算,或包含不同类型的事件。从技术上讲模式是完整的,但业务语义已改变。这是最危险的漂移类型,因为自动化测试通常无法捕捉到它。

流程漂移:第三种漂移类型。validation.md、评审者报告或其他流程产物未反映模型的实际变更。例如:评审者批准了变更,但忘记更新测试覆盖率矩阵或 changelog 中的记录。从形式上看流程已通过,但实际上契约的证据基础已丢失。

契约的证据基础:原则:如果 ODCS 声明某个字段为必填(例如 not_null),那么必须存在至少一个 dbt 测试、singular 测试或评审者的手动事实来确认此属性。没有证据的契约仍然是一个无法被证实或反驳的承诺。

最小化契约影响说明:应出现在每个涉及模型契约的 PR 描述中的结构化 markdown 块。包含六个字段:变更的字段、是否为破坏性变更、对 PII 的影响、对 SLA 的影响、验证覆盖情况、是否需要人工确认。这是证明 PR 作者已意识到契约后果的证据。

Grain 作为接口的一部分:原则,根据该原则,grain(例如"每个 customer_id 一行")是数据仓库公共接口的一部分,就像列的列表一样。更改 grain 是破坏性变更,即使模式保持不变。这是理解语义漂移的核心概念。

用于查找漂移的 Qwen 查询:给 LLM 助手的提示,用于比较四个产物:specs/customer_360_contract.odcs.yaml、specs/models/mart_customer_360.md、models/schema.yml 和 models/marts/mart_customer_360.sql。助手应查找漂移和测试覆盖率的缺口,同时禁止修改文件。结果是一份给评审者的分析说明。

重要日期: odcs 标准的出现:ODCS(Open Data Contract Standard)规范自 2023 年以来由社区积极开发,旨在通过类比 OpenAPI 与 REST 来统一机器可读的数据契约。

dbt contracts 的引入:dbt 中的 contract.enforced 功能出现在 1.5 版本(2023 年末),并在 1.6+ 中稳定,支持部分适配器的 table 和 incremental 物化。

Data mesh 概念(Zhamak Dehghani):自 2019-2020 年起,data mesh 概念将数据契约作为 data-as-a-product 的强制元素,影响了 ODCS 和 dbt 契约的出现。

数据契约概念的演变:在 2022 年之前,"data contract" 一词更多是营销性的;自 2023 年起,它已成为大型公司(GoCardless、Shopify、Netflix)中正式化的工程产物。

练习题: 标题:ODCS 承诺与 dbt 测试的匹配

题目:给定两个片段。片段 A 是 specs/customer_360_contract.odcs.yaml 的摘录:

columns:
  - name: customer_id
    dataType: integer
    required: true
    primaryKey: true
  - name: risk_event_count_7d
    dataType: integer
    required: true
  - name: email
    dataType: string
    required: false
    classification: PII

片段 B 是 models/schema.yml 的摘录:

models:
  - name: mart_customer_360
    columns:
      - name: customer_id
        tests:
          - unique
          - not_null
      - name: risk_event_count_7d
        tests:
          - not_null
      - name: email
        # 没有测试

列出必填字段,并指出每个字段由哪个 dbt 测试、singular 测试或评审者的手动事实所覆盖。对于没有覆盖的字段,编写一个 singular 测试或评审者动作。

解答:步骤 1. 根据 ODCS 确定必填字段:customer_id(required、primaryKey)和 risk_event_count_7d(required)。email 字段不是必填的。

步骤 2. 与 schema.yml 中的测试进行匹配:

  • customer_id:由 schema.yml 中的 unique 和 not_null 测试覆盖。ODCS 的两个承诺(required + primaryKey)都由自动化测试关闭。
  • risk_event_count_7d:由 not_null 测试覆盖。required 承诺已关闭,但未证明值是否合理(例如 ≥0)。需要针对范围的额外 singular 测试。

步骤 3. 对于 email 字段(在 ODCS 中归类为 PII,但未由测试覆盖):要么需要 singular 测试以防止在意外位置出现 email,要么需要明确的评审者事实:"email 未在 mart 中使用,该列已预留"。此事实记录在 validation.md 中。

步骤 4. 最终覆盖率表:

字段ODCS 承诺覆盖率
customer_idrequired + primaryKeyunique + not_null ✅
risk_event_count_7drequirednot_null ✅,但没有范围检查 ⚠️
emailPII没有测试 ⚠️,需要 singular 测试或评审者事实

难度:中级

标题:漂移类型的分类

题目:对于以下四种情况,确定漂移类型(架构漂移、语义漂移、流程漂移)并论证变更是否为破坏性变更。

情况 1:在 mart_customer_360 中添加了一个可为空的 product_code 列。 情况 2:mart_finance 中的 total_revenue 指标被重命名为 net_revenue 并现在减去退款。 情况 3:评审者批准了一个变更 grain 的 PR,但未更新 validation.md。 情况 4:在 ODCS 规范中 email 字段被标记为 PII,但在 schema.yml 中已删除,而 SQL 中仍保留。

解答:情况 1:架构漂移。列被添加——ODCS 和 schema.yml 不一致。对现有消费者而言此变更不是破坏性的(他们只是忽略新列),但可能在语义上重要:product_code 可能被视为新的业务指标。需要在 PR 中明确描述。

情况 2:语义漂移 + 架构漂移。字段被重命名(架构),其含义也发生了变化(减去退款,语义)。这是破坏性变更:任何引用 total_revenue 的仪表板要么会中断,要么会在没有明显错误的情况下开始显示不同的数字。

情况 3:流程漂移。更改进入代码,评审者已批准,但流程产物(validation.md)未更新。变更本身可能是任何类型,但证据基础已丢失。如果变更在契约上有意义但验证未记录,则视为破坏性变更。

情况 4:架构 + 流程漂移。ODCS 表示 PII 字段存在;schema.yml 表示不存在;SQL 表示存在。三个来源不一致。从流程的角度看这是破坏性变更:读取 schema.yml 的消费者会认为 email 不存在,并且可能不应用脱敏处理。

难度:中级

标题:撰写最小化影响说明

题目:您是 PR 的作者。变更:在 mart_customer_360 中添加了一个可为空的 middle_name(父名)列,grain 未更改,customer_id 仍是 primary key,未添加新测试,PII 策略未受影响(middle_name 在 ODCS 中归类为 PII)。按照章节中的模板填写最小化契约影响说明。

解答:```markdown 变更的字段:添加了可为空的 middle_name 列(PII) 破坏性变更:否(可为空,不更改 grain,现有消费者不受影响) 对 PII 的影响:在 mart 中添加了 PII 列 middle_name;需要检查下游消费者是否考虑了此分类;该字段在 schema.yml 中应有 classification: PII,以及禁止未经脱敏传播的测试或 singular 规则 对 SLA 的影响:无(未影响新鲜度和完整性) 验证覆盖情况:为 middle_name 添加了 singular 测试,检查该列仅出现在访问级别为 confidential 的 marts 中;customer_id 仍由 unique + not_null 覆盖 是否需要人工确认:是的,由于添加了新的 PII 列,需要 data steward 的明确确认

关键点:即使是非破坏性变更,如果涉及 PII 或可能改变消费者的预期,仍需要人工确认。

难度:中级

标题:为具体模型设计 Qwen 提示

题目:您有一个带有 specs/orders_contract.odcs.yaml 中契约的 mart_orders 模型。为 Qwen Code 设计一个提示,以检查该模型的契约漂移。提示应:列出所有四个要比较的产物,明确指出不能修改文件,并要求提供结构化结果。

解答:```text
比较 specs/orders_contract.odcs.yaml、
specs/models/mart_orders.md、models/schema.yml 和
models/marts/mart_orders.sql。
查找契约漂移和测试覆盖率的缺口。
不要修改文件。
以以下形式输出结果:
1. 按 ODCS 列出的必填字段及其测试覆盖情况。
2. SQL 中存在但未在 ODCS 中描述的字段列表。
3. ODCS 中已描述但在 SQL 中缺失的字段列表。
4. 检测到的语义不一致(grain 变更、指标重命名、计算逻辑变更)。
5. 按章节模板的最小化契约影响说明。

正确提示的关键要素:明确的文件列表、明确的修改禁令、结构化输出、与章节概念(drift、覆盖率、grain)的关联。

难度:中级

标题:识别破坏性变更

题目:给定带有契约的模型中的五项变更。对于每项,确定它是否为破坏性变更,以及是否需要 data steward 的确认。

  1. 将 status 列重命名为 order_status。
  2. 添加可为空的 discount_amount 列。
  3. 将 grain 从"每个 customer_id 一行"更改为"每个 (customer_id, month) 一行"。
  4. 删除 6 个月未使用的 legacy_score 列。
  5. 将 ltv_90d 的计算公式从 sum(amount) 更改为 sum(amount) - sum(refund)。

解答:1. 破坏性变更。重命名会破坏任何引用 status 的仪表板。需要 steward 的确认和迁移期。

  1. 不是破坏性变更(形式上)。可空列不会破坏现有消费者。但如果 discount_amount 被视为新的业务指标,则需要通知并可能需要 product owner 的确认。
  1. 破坏性变更。更改 grain 是最危险的破坏性变更类型:行数发生变化,下游聚合产生不同的数字。需要 steward 的强制确认和产品级协调。
  1. 有条件的破坏性变更。如果 legacy_score 确实未被使用,删除是安全的。但"6 个月未使用"是薄弱的证据:仪表板可能已被归档而非删除。需要 product owner 的确认。
  1. 破坏性变更。语义变更:数字在没有重命名列的情况下发生变化。这是最糟糕的情况——测试可能通过,消费者看到不同的数据。需要 steward 的确认,强制通知消费者,更新 ODCS 和范围测试。

难度:高级

案例研究: 标题:添加"安全"列破坏了报表

场景:金融科技公司,约 200 名员工,dbt 中有 12 个 marts。mart_customer_360 数据仓库被风险管理团队用于高风险客户的每日报告。ODCS 中的契约将 grain 描述为"每个 customer_id 一行",并列出 14 个字段,其中包括 risk_event_count_7d(必填)。Tableau 中的仪表板按 customer_id 进行聚合,并计算 risk_event_count_7d > 5 的客户比例。

挑战:分析师在模型中添加了一个可为空的 product_code 列(客户主要产品的代码),认为此变更是安全的,并在未更新 ODCS 的情况下创建了 PR。dbt 测试通过,评审者未经质疑即批准。三周后,风险管理团队发现"高风险客户"报告现在包含的记录数增加了 2.3 倍。调查显示:在 Tableau 计算中意外地将 product_code 用作额外的分组键,报告的 grain 隐式地从 customer_id 变为 (customer_id, product_code)。

解决方案:团队实施了三个流程变更。首先,在 PR 模板中添加了根据章节示例的强制性"契约影响说明"块。其次,将 Qwen Code 配置为 CI 中的自动检查:对于每个涉及带有 ODCS 的模型的 PR,机器人会比较四个文件并留下关于漂移嫌疑的评论。第三,引入规则:如果 PR 向带有契约的模型添加可空列,评审者必须明确确认新列不会更改 grain,也不会被视为业务指标。

成果:在实施后的第一季度,机器人在合并前发现了 7 个潜在的漂移,其中 2 个是真实的语义问题,这些问题在常规 dbt 测试中会被遗漏。由于作者自行填写影响说明且评审者无需花时间弄清"这个列是什么意思",PR 评审的平均时间缩短了 18%。

经验教训: 可空列不等于安全变更:如果它被视为新的分组键,则 grain 会隐式更改

自动化 dbt 测试无法捕捉 grain 变更——这需要单独的键唯一性测试或评审者的明确事实

契约影响说明作为清单发挥作用:填写它这一行为本身就迫使 PR 作者考虑消费者

相关概念: 架构漂移

语义漂移

Grain 作为接口的一部分

最小化影响说明

用于查找漂移的 Qwen 查询

标题:指标公式的静默变更

场景:电子商务平台,mart_finance_daily 数据仓库被财务团队用于收入计算。gross_revenue 指标在 ODCS 中被描述为"当天所有成功完成订单的金额总和"。SQL 将其实现为 SUM(amount) FROM fct_orders WHERE status = 'completed'。该数据仓库被 Looker 中的三个仪表板和一个用于收入预测的 ML 管道所使用。

挑战:工程师修改了公式,添加了过滤器 on NOT refunded_at IS NULL,以"在总营收中不考虑退款"。此变更作为"逻辑的小幅改进"通过评审。ODCS 未更新,schema.yml 未触及,dbt 测试通过。一个月后,财务团队发现数据仓库报告与 ERP 系统报告相差 12%。调查发现,部分退款以前被计入 gross_revenue,而现在不再计入。

解决方案:团队停止了变更,将 SQL 回滚到之前的公式,更新了 ODCS,并添加了新的 net_revenue 指标,明确描述为"成功完成且未退款的订单金额总和"。在 schema.yml 中添加了单独的测试,检查 gross_revenue ≥ net_revenue 对于任何一天均成立。在 PR 模板中添加了警告:"如果 SQL 变更影响 ODCS 中描述的指标计算,则该变更被视为破坏性变更,需要 data steward 的确认。"

成果:在事件发生后的六个月内,没有发生任何静默指标变更的情况。用于收入预测的 ML 管道继续无故障运行。团队正式确定了政策:"指标公式的任何变更 = 破坏性变更 = 需要主版本契约并在 2 周前通知所有消费者。"

经验教训: 语义漂移比架构漂移更危险:模式完整,测试绿色,数字静默变化

逻辑改进 ≠ 如果逻辑在契约中描述,则为安全变更

契约必须明确列出指标及其公式,否则任何"优化"都是漂移

相关概念: 语义漂移

破坏性变更

ODCS 作为承诺

契约的证据基础

标题:没有测试的 ODCS:漂亮但已死产品的契约

场景:保险公司,遗留项目。数据治理团队的分析人员为 30 个数据仓库创建了 ODCS 规范,隆重地将其提交到存储库,并向管理层报告了"数据契约的引入"。数据仓库继续照常开发:未与 ODCS 关联,schema.yml 仅描述了列的一小部分,测试很少。

挑战:半年后审计显示,30 个 ODCS 规范中有 18 个已完全过时:字段已重命名,指标已重新计算,grain 已更改。然而从形式上讲契约是存在的,向监管机构的报告看起来是"绿色"的。法律和合规部门使用 ODCS 作为"一切尽在掌握"的证据,但事实上消费者收到的数据与契约不符。

解决方案:团队引入了"ODCS 承诺 → dbt 测试或 singular 测试或评审者事实"的关联。对于 ODCS 中的每个必填字段,至少创建了一个测试。在 CI 中添加了检查:如果 ODCS 中指定了 required: true,则 schema.yml 中必须有 not_null 测试。如果 ODCS 中指定了 classification: PII,则 schema.yml 或 singular 测试中必须有明确的处理规则。所有 18 个过时的规范要么更新为数据仓库的实际状态,要么标记为 deprecated 并附有明确的 SLA 直至停用。

成果:一个季度内,当前 ODCS 中 100% 的必填字段获得了测试覆盖。监管机构审计在数据契约方面无异议。团队总结了原则:"没有证据基础的 ODCS 不是契约,是营销。"

经验教训: ODCS 本身没有价值:当每个承诺都可验证时,价值才会出现

契约必须与实现同步,否则它成为错误自信的来源

ODCS ↔ schema.yml ↔ SQL 的关联必须在 CI 中自动化,否则漂移不可避免

相关概念: 契约的证据基础

两个契约层级

流程漂移

契约与测试的关联

学习技巧: 像评审者而不是作者那样阅读变更。作者看到的是"SQL 是否变得更好",评审者看到的是"承诺是否保持不变"。在每个涉及带有契约的模型的 PR 中切换视角。

记住最小化影响说明的六个字段,并将其作为清单放在脑海中:变更的字段、是否为破坏性变更、对 PII 的影响、对 SLA 的影响、验证覆盖情况、是否需要人工确认。这是本章最实用的技能。

区分"测试通过"和"契约得到遵守"。测试在某个时间点检查一列的一个属性。契约是数据仓库整个生命周期中承诺的集合。绿色的测试并不意味着契约是活的。

练习设计 Qwen 提示:以您的工作中的模型为例,要求 LLM 助手比较 ODCS、模型描述、schema.yml 和 SQL。评估它发现了哪些漂移,遗漏了哪些漂移,并完善提示。此技能是可扩展的。

不要混淆架构漂移和语义漂移。架构漂移是关于列的列表。语义漂移是关于那些保留的列的含义。语义漂移的危险性是前者的 5-10 倍,因为自动化测试通常无法捕捉到它。

在添加可空列时问自己:"如果我是消费者,我会将此列视为新的业务指标吗?" 如果是——这不是"安全"的变更,即使它是可空的。

记住 grain 是接口的一部分。更改 grain 始终是破坏性变更,即使所有列保持不变。对 grain 键的 unique 测试是任何带有契约的模型的强制卫生要求。

不要在第一次学习中启用 contract.enforced: true。首先学习通过肉眼和 Qwen Code 阅读漂移。适配器的机械约束是第二步,届时团队会理解为什么需要它。

附加资源: Odcs 官方规范:https://bitol-io.github.io/data-contract-specification/ — 课程中使用的 Open Data Contract Standard 标准的主要来源

Dbt 契约文档:https://docs.getdbt.com/docs/mesh/contracts — 关于 contract.enforced 及受支持物化的 dbt 官方文档部分

Zhamak Dehghani 关于 data mesh 的文章:https://martinfowler.com/articles/data-mesh-principles.html — data-as-a-product 和数据契约的概念基础

Dbt-utils 包:https://github.com/dbt-labs/dbt-utils — 扩展契约检查覆盖范围的标准测试和宏

Great expectations:https://greatexpectations.io/ — 可用作 dbt 测试之外额外检查层的数据质量断言框架

Базовый data:https://www.elementary-data.com/ — 用于 dbt 的开源数据可观察性,包括异常和测试

《Data Contracts》一书(Andrew Jones):https://datacontract.com/ — 在组织中实施数据契约的实用指南

Qwen code 文档:https://qwenlm.github.io/ — 关于使用 LLM 助手分析代码和规范的材料

总结:数据契约是数据仓库的产品承诺与其 SQL 实现之间的可验证层。在 SDD Data 中区分两个层级:ODCS 描述产品(模式、质量、SLA、PII 策略),schema.yml 和 dbt 测试描述具体模型。没有哪一层可以替代另一层:没有 ODCS 的测试会产生语义错误的绿色模型,没有测试的 ODCS 会产生没有证据的漂亮契约。当代码变更而契约保持不变时,就会发生契约漂移,并有三种类型:架构漂移(字段被添加/删除/重命名)、语义漂移(字段含义在模式不变的情况下发生变化)和流程漂移(流程产物未反映变更)。语义漂移是最危险的,因为自动化测试通常无法捕捉到它。主要的实践技能是在每个 PR 中填写最小化契约影响说明,并为自动查找漂移设计 Qwen 提示。记住说明的六个字段:变更的字段、是否为破坏性变更、对 PII 的影响、对 SLA 的影响、验证覆盖情况、是否需要人工确认。并记住主要原则:添加可空列不等于安全变更,如果它被视为新的业务指标或更改了 grain。

我的笔记
0 / 10000

笔记保存在当前浏览器中。在其他设备上将不会显示。

课程菜单

课程

SDD Data. 使用 Qwen Code 和 dbt 构建银行数据平台
进度 0 / 110