学习指南: 第1部分. 简介:面向数据的SDD

模块「第1部分. 简介:面向数据的SDD」中第 3 / 5 节课
您正在未登录状态下查看课程。 请登录,以保存进度并参加测试。

主题: 第一部分。引言:数据的 SDD

难度等级: 中级

预计学习时间: 2–3 小时(理论 + 本地项目实践)

前置要求: SQL 基础知识(SELECT、JOIN、GROUP BY、聚合函数)

理解数据仓库与数据集市(marts)的概念

熟悉第一卷中介绍的 Spec-Driven Development(SDD)方法论

对 dbt 有基本了解(模型、sources、测试)

熟练使用命令行与 Git

学习目标: 阐明 SDD Data 与传统 dbt 课程的主要区别:工作顺序是从使命和规约出发,再到 SQL,而非相反。

解释为什么数据语义不可见的变化(grain、指标、新鲜度、PII)比 Web 应用代码错误更危险。

确定数据产品规约作为唯一真相来源的角色,以及 dbt 作为已固化承诺执行者的定位。

列出 SDD Data 的三条基本规则:规约存放在代码仓库中、验证事实必须带有一条命令或一个评审者步骤、作者与评审者必须在不同的模式下工作。

在本地运行基于 DuckDB 的教学 DataLakeHouse 烟雾测试,并确定自己的课程阅读模式。

概述: 本书第一部分将 Spec-Driven Development 方法论引入数据平台领域。如果第一卷中的 SDD 保护 Web 应用的功能免受代理在路由和 UI 上出错,那么这里它保护的是数据的语义:数据集市的粒度、指标的正确性、新鲜度、个人数据策略以及与下游消费者的兼容性。本部分的核心论点:dbt 仍然是主要的转换工具,但不是语义的来源。语义存在于规约之中,SQL 必须执行已固化的承诺,而不是事后创造它。

教学项目是虚构的银行「北桥银行」的 DataLakeHouse。到本书末尾,读者将搭建一个基于 DuckDB 的本地 dbt 项目,产出五个最终的数据集市:mart_customer_360、mart_card_turnover_daily、mart_payment_risk_signals、mart_credit_portfolio_quality 和 mart_open_api_consents。所有数据均为合成数据,俄罗斯银行业背景用作一组约束条件:PII、支付、同意、风险信号、监管报告。这是一个可复现的教学架构,而非法律指引。

本部分奠定了全书的节奏:先记录数据产品的承诺,再通过 dbt 使其可执行,然后用事实加以证明,并交付独立评审。在第一章层面,要求读者选择阅读模式、运行烟雾测试,并在工作笔记中固定以下认识:本书构建的是一个可复现的数据产品流程,而不是笼统的「银行」。

关键概念: SDD 数据:一种开发循环,其中代理不是从实现开始。先由人类与代理固定意图、边界、决策与验证事实,然后代理按照已批准的文件编写代码或 SQL,最后由单独的评审者核查的不仅是测试是否通过,还包括结果是否与规约一致。对数据而言,该循环如下:描述数据产品 → 定义契约 → 实现 dbt 模型 → 执行验证事实 → 评估对契约的影响 → 汇总发布证据。

数据产品规约:一份面向消费者固化承诺的文件:承诺的 grain 为何、哪些字段是必需的、哪些是禁止的、哪些校验允许变更、谁确认漂移或破坏性变更。它存放在代码仓库中而非聊天中,并作为唯一的真相来源。

数据契约:数据集市的供应方与消费者之间关于其结构与语义的形式化约定。契约的任何变更都必须被明确记录并获得批准,否则视为破坏性变更。

数据集市的 grain(粒度):描述表中一行所代表语义单元的维度(例如「一个客户」或「一个客户-产品」)。grain 的变化是语义破坏性变更不可见的典型例子。

不可见的语义变化:一种缺陷,其中 SQL 编译通过、dbt 测试也通过,但下游消费者获得的行语义已不同。在 Web 应用中这种缺陷常常立即可见,而在数据中,它可能在一周后进入报表、KPI 或风险模型。

验证事实:一条命令、一个 SQL 查询或评审者的人工步骤,用于证明 dbt 模型确实执行了规约,而不仅仅通过了测试。没有验证事实,「dbt build 全绿」并不等于正确的数据集市。

Dbt 作为承诺执行者:本书对 dbt 的定位——一种使行为可被验证的转换工具,但不决定业务上什么应当为真。类似于普通应用中的测试框架。

「北桥银行」数据湖仓:基于 DuckDB、可在本地复现的教学项目。包含五个数据集市:mart_customer_360、mart_card_turnover_daily、mart_payment_risk_signals、mart_credit_portfolio_quality、mart_open_api_consents。所有数据均为合成数据。

SDD 数据的三条基本规则:(1)规约存放在代码仓库中,而非聊天中。(2)验证事实必须带有命令、SQL 或评审者的人工步骤。(3)作者与评审者必须在不同模式下工作,即使他们是同一 CLI 代理在 /clear 之后的两次会话。

PII 与个人数据策略:本书在 SQL 之前引入规约所要应对的约束类别。在 DataLakeHouse 中,个人数据、同意与风险信号正是「快速写 SQL」无权做决定的领域。

消费者链:解释语义错误如何蔓延的隐喻:运营团队提取来源,数据工程师规整类型,分析师构建报表,风险团队查看信号,管理者阅读聚合。本地的 SQL 错误由此转化为系统性的语义错误。

烟雾测试与本地轨道:位于 book3/examples/bank-lakehouse 的命令 bash smoke_all.sh。在没有 dbt 时生成合成数据并检查结构,安装 dbt 后执行完整的 dbt build。这是第一部分的第一个实践步骤。

契约漂移与破坏性变更:漂移指数据集市的实际行为逐渐偏离规约。破坏性变更是指改变 grain、必需字段或指标语义,从而违反与消费者的契约。二者都需要所有者明确确认。

重要日期: 阶段 0:运行 smoke_all.sh:第一个实践步骤:在本地运行烟雾测试,确认 DataLakeHouse 能在读者的机器上复现。

阶段 1:固定阅读模式:决定读者所走的轨道:基于 DuckDB 的本地教学轨道,或将战斗生产环境作为参考层进行对照。

阶段 2:工作笔记:在 markdown 文件中固定:「第一遍:在 dbt-duckdb 上搭建本地北桥银行 DataLakeHouse;在通过实践考核之前不引入与生产环境的对照」。

练习题: 名称: 练习 1。运行 DataLakeHouse 本地烟雾测试

问题: 克隆本书仓库,进入 book3/examples/bank-lakehouse 目录并执行 bash smoke_all.sh。首先在未安装 dbt 的情况下运行——确认脚本生成了合成数据并检查了结构。然后安装依赖(dbt-core、dbt-duckdb),执行完整的 dbt build。在笔记中记录创建的表列表和执行耗时。

解决方案: 步骤 1:git clone <本书仓库> && cd <本书目录>。步骤 2:cd book3/examples/bank-lakehouse。步骤 3:bash smoke_all.sh——在无 dbt 的情况下,脚本应将合成 parquet/csv 生成至 data/ 目录,并打印结构报告。步骤 4:pip install dbt-core dbt-duckdb(或使用你的包管理器对应的等价命令)。步骤 5:再次运行 bash smoke_all.sh——应执行 dbt build 并以绿色结束。步骤 6:查看 dbt_project.ymlmodels/ 目录,找到五个最终的数据集市。在笔记中记录构建耗时以及每个 mart 表的行数观察。

难度: 初级

名称: 练习 2。「grain 的不可见变化」情景

问题: 打开数据集市 mart_customer_360 的规约(或描述),以书面形式固化其承诺:grain 为何、哪些是必需字段、哪些是禁止字段、谁是消费者。然后设想在 SELECT 中给代理增加字段 product_code,描述基于该集市统计活跃客户数的消费者契约将如何被打破。准备论证:为什么即便 dbt build 全绿也无法保护报表。

解决方案: 步骤 1:记录 grain——「一行对应一个 customer_id」。步骤 2:列出必需字段,例如:customer_id、full_name、birth_date、segment、opened_at。步骤 3:列出禁止字段,例如:card_pan、phone、email、各类同意。步骤 4:描述消费者——例如「按 segment 统计活跃客户数」的报表,其执行 select segment, count(distinct customer_id) from mart_customer_360 group by segment。步骤 5:加入 product_code 后,同一查询在没有 distinct 时开始按「客户-产品」配对计数,而带 distinct 时则不再考虑客户拥有多种不同类型产品的情况。步骤 6:论证:诸如 not_nullunique 作用于 customer_id 的 dbt 测试仍会通过,因为(若数据中没有客户重复)customer_id 仍然唯一,但报表会显示被夸大的数字并改变语义。

难度: 中级

名称: 练习 3。设计一条验证事实

问题: 为数据集市 mart_card_turnover_daily 编写一条验证事实,以 SQL 查询形式证明:数据集中某一天的日合计营业额与 raw 来源中同一日的合计一致,允许误差 ±0.01。该查询应保存在文件 tests/facts/mart_card_turnover_daily_recon.sql 或同类位置。以文字说明它证明了什么,以及在何种差异下被视为破坏性。

解决方案: 步骤 1:确定来源——例如 stg_card_transactions。步骤 2:编写对账:with mart_sum as (select transaction_date, sum(amount) as mart_total from {{ ref('mart_card_turnover_daily') }} group by transaction_date), src_sum as (select transaction_date, sum(amount) as src_total from {{ source('core','card_transactions') }} group by transaction_date) select m.transaction_date, m.mart_total, s.src_total, abs(m.mart_total - s.src_total) as diff from mart_sum m join src_sum s using(transaction_date) where abs(m.mart_total - s.src_total) > 0.01。步骤 3:空结果即成功。步骤 4:以文字固定:「若任何一天的 diff > 0.01,则视为该事实失败,发布被阻断,所有者为风险团队」。步骤 5:将该文件作为 data_tests 加入 sources.yml,或在 CI 中接入。

难度: 中级

名称: 练习 4。识别数据集市中的 PII

问题: 打开数据集市 mart_open_api_consents 的 schema(SQL 或描述)。列出你将归类为 PII 或敏感的字段。说明其中哪些可以交付给消费者、哪些需要脱敏、哪些完全禁止。准备一份应伴随该数据集市的简短校验清单。

解决方案: 步骤 1:列出全部字段。步骤 2:进行分类。例如:client_id——准标识符,在聚合维度上允许;consent_id——允许;consent_type、granted_at、expires_at——允许;ip_address、user_agent、device_id——禁止或脱敏;full_name、phone、email——禁止;reason_for_withdrawal——在聚合中允许。步骤 3:为每一类描述其处理方式。步骤 4:列出校验项:phone/email 不存在的测试、必需 consent_id 存在的测试、数据集新鲜度测试(不超过 N 小时)、revoked 与 granted 占比的检查。步骤 5:附上所有者清单:client_id——数据所有者为客户服务,consent_id——所有者为开放 API 产品。

难度: 中级

名称: 练习 5。另一种模式下的评审

问题: 设想你已经为 mart_payment_risk_signals 写好了规约与 dbt 模型。现在模拟「另一种模式」下的评审:以 /clear 开启 CLI 代理的新会话,明确禁止自己修改模型,并只请求其检查与规约的一致性。记录你将向评审者提出的问题以及要求其提供哪些证据。

解决方案: 步骤 1:描述被评审的工件——规约路径、模型路径、测试路径。步骤 2:固化禁令——「不得编辑 model.sql 与规约,只进行分析」。步骤 3:评审问题清单:grain 是否与承诺一致;所有必需字段是否存在且非 null;SELECT 中是否出现禁止字段;验证事实是否通过;与 raw 来源在合计/计数上是否存在偏差。步骤 4:所需证据——验证事实的输出、测试清单及状态、dbt 项目相对 main 的 diff、带版本的 spec 文件链接。步骤 5:固定结论——approved / changes_required / blocked。

难度: 高级

案例研究: 名称: 案例 1。客户数据集市在新增产品后「膨胀」

场景: 一家中型银行,数据平台基于 dbt + Snowflake。数据集市 mart_customer_360 已稳定服务仪表板「按 segment 统计活跃客户数」以及监管报表长达半年。分析团队请数据工程师在数据集中新增字段 product_code,以便 BI 侧无需再连接产品字典表。

挑战: 数据工程师与 AI 助手一起在 SELECT 中添加了 product_code,跑了 dbt build,作用于 customer_idnot_nullunique 测试均通过(因为在数据快照日每个客户仅有一种产品)。发布进入生产。一周后,监管报表显示的「活跃客户数」比实际多出 18%——拥有两种及以上产品的客户其行被复制了。按 segment 的仪表板因使用了人工 distinct 仍然显示正确数字。

解决方案: 团队停发了该版本,在 dbt 项目中回滚了该变更,新增了独立的分析层 mart_customer_products,其 grain 为「客户-产品」;而在 mart_customer_360 中保留 grain 为「客户」。事故后团队引入了 SDD 实践:每个数据集市在代码仓库中都附带一份 YAML 规约,明确 grain、必需字段与禁止字段。每个 PR 由评审者在独立的代理会话中核对 diff 与规约。

结果: 事故代价——三天的监管报表人工重算以及对合规部门的解释说明。引入 SDD Data 后,类似变更会在评审阶段被阻断:评审者注意到新增字段改变了 grain,并要求要么拆到独立 mart,要么明确描述漂移并获得仪表板所有者的签字。

经验教训: dbt build 全绿并不等于数据集市契约被守护。

Grain 是规约的一部分,而非 SQL 的属性。

「另一种模式」(独立代理会话)的评审能够捕捉到作者因上下文而忽略的问题。

相关概念: 数据集市的 Grain(粒度)

不可见的语义变化

数据产品规约

契约漂移与破坏性变更

名称: 案例 2。PII 通过分析型集市泄露

场景: 某大型金融科技公司,基于 dbt + BigQuery。开放 API 团队通过 BI 工具为外部合作伙伴准备数据集市 mart_open_api_consents。AI 助手响应「搭建一个字段尽可能全的同意相关集市」的请求,将 client_emailclient_phone 一并加入 SELECT,认为它们「对分群有用」。

挑战: 合作伙伴在仪表板上看到了客户的邮箱和电话,尽管并未获得向合作伙伴传输这些数据的同意。法务部门记录了一起个人数据处理事故,随后开出罚单并被要求通知客户。

解决方案: 使用字段白名单重新构建了集市,白名单由规约定义。在 dbt 中新增测试,一旦 schema 中出现「红名单」字段即失败。代码仓库中新增了 YAML 规约,包含 allowed_fieldsforbidden_fields 章节。任何扩展 SELECT 的 PR 都必须强制对照禁止字段清单进行检查。

结果: 事故代价——监管罚款、声誉损失、合作伙伴 API 重建。引入 SDD Data 后,PII 政策在 SQL 之前就被描述清楚,而不是在发布之后通过诉讼去发现。规约与模型同处代码仓库,任何团队成员都能看到边界。

经验教训: 「字段尽可能全」对没有规约约束的代理来说是一条危险指令。

SQL 中的禁止比分析师脑中的禁止更容易被检验。

PII 政策是一种契约,而非最佳实践。

相关概念: PII 与个人数据策略

数据契约

数据产品规约

SDD Data 的三条基本规则

名称: 案例 3。支付风险信号上不易察觉的 freshness 偏移

场景: 某中型银行的风险平台,基于 dbt + ClickHouse。数据集市 mart_payment_risk_signals 每 15 分钟刷新一次。代理在重构 DAG 时调整了调度,导致数据集市的实际新鲜度从 15 分钟扩大到 47 分钟。dbt 测试仍然通过,因为数据在绝对意义上仍是新鲜的(不超过一天)。

挑战: 风险团队在两周后才发现这一偏移——彼时反欺诈评分模型已开始漏掉夜间攻击的高峰:集市根本没来得及同步最新交易。虽然避免了直接事故,但欺诈信号的平均发现时间显著上升。

解决方案: 在 mart_payment_risk_signals 的规约中新增了显式的 freshness 章节,规定上限为 20 分钟。在 dbt 项目中新增一条验证事实,将数据集市的 max(updated_at) 与当前时间进行比较,差距超过 20 分钟即失败。该事实被纳入发布证据的必选项。

结果: 代价——两周的「盲区」以及风险分析师数十小时的复盘。引入 SDD Data 后,freshness 的偏移在一个发布周期内就会被发现,而非在两周的运行之后。Freshness 是契约的一部分,而非运营层面的细节。

经验教训: Freshness 是一种语义承诺,而非 ITSM 中的 SLA。

「不超过一天」的测试不足以保护业务关键型集市。

Freshness 验证事实应与规约一同存放在代码仓库中。

相关概念: 验证事实

数据产品规约

契约漂移与破坏性变更

消费者链

学习建议: 不要急于将 DuckDB 替换为生产栈。第一遍更重要的是看清「规约 → dbt → 验证事实」这条链路,而不是搭建生产级基础设施。

阅读时请保持两样东西同时打开:章节正文以及本地项目的 models/。将理论中的每个论断与代码仓库中的相应文件进行对照。

在让代理编写 SQL 之前,先自己写下 5–10 行规约:grain、必需字段、禁止字段、消费者、freshness。这一练习能最快体现 SDD Data 与普通 dbt 课程的差异。

建立独立文件 notes/sdd-data-checks.md,将每一部分的检查问题汇总进去。开始下一章前回到该文件,尝试不靠提示自行作答。

与代理协作时,务必为评审者角色开启新的会话(/clear)。作者与评审者应在不同模式下工作——这是规则 #3,它能节省大量排查时间。

每次变更后都运行 bash smoke_all.sh。如果烟雾测试变黄或变红,不要「凭直觉」修改 dbt 模型——先重读规约与验证事实。

在进入下一部分前重读「首要风险:不可见的语义变化」一节。该情景会以不同形式反复出现,一旦陷入 mart 细节就很容易遗忘。

不要混用教学轨道与生产环境。如果你已有运行中的 dbt 项目,请将其作为参考层进行对照,但不要在第一遍时就把 SDD Data「应用」到生产。

附加资源: 本书仓库的 book3/examples/bank-lakehouse 目录:包含北桥银行 DataLakeHouse 的 dbt 项目、合成数据以及脚本 smoke_all.sh。这是课程的主要实践工件。

dbt 文档(dbt labs):关于模型、测试、sources、exposures 与宏的参考手册。作为字典使用,而非教科书——概念框架由本书提供。

Dbt-utils 与 dbt-expectations:包含现成测试的包,可用于构建验证事实(如 expect_column_values_to_be_between、recency、新鲜度)。

本系列第一卷(面向 Web 应用的 SDD):SDD 方法论的源头。如果你跳过了第一卷,请阅读其引言章节——「描述 → 规约 → 实现 → 验证 → 评审」这一循环在两卷中是一致的。

数据产品规约清单(位于 book3/templates/ 的模板):YAML/JSON 模板,包含 grain、必需/禁止字段、freshness、所有者、验证事实等章节。复制并适配到你自己的项目中。

俄罗斯央行关于监管报告的标准(152-ФЗ、信息安全条例):DataLakeHouse 所处的背景。不是法律指引,而是使规约成为强制项的约束来源。

摘要: 第一部分引入 SDD Data 方法论并固定其核心论点:在数据平台中,语义重于代码。代理可能悄无声息地改变 grain、指标、freshness、PII 策略或与消费者的兼容性,而全绿的 dbt build 捕捉不到这些。因此工作顺序与普通 dbt 课程相反:先有平台使命,再有数据产品规约与契约,然后是 raw/staging,再到 marts,最后是验证事实与独立评审。dbt 仍然是主要的转换工具,但语义的来源是存放在代码仓库中的规约。从第一章起就要记住三条规则:规约存放在代码仓库中、验证事实带有命令或评审者步骤、作者与评审者在不同模式下工作。教学项目是北桥银行基于 DuckDB 的 DataLakeHouse,最终产出五个数据集市(mart_customer_360、mart_card_turnover_daily、mart_payment_risk_signals、mart_credit_portfolio_quality、mart_open_api_consents)。本部分的最小产出是:选择阅读模式、运行本地烟雾测试,并在笔记中固定「本书构建的是一个可复现的数据产品流程,而非笼统的『银行』」这一认识。

我的笔记
0 / 10000

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

课程菜单

课程

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