跳转至

Ch 12 配置驱动的任务模型

面包屑

本书主页Part III 数据工程实践 › Ch 12

项目第 1 年 · 核心建设期——任务模型设计


本章你将学到

  • 任务声明模型:以业务域与数据实体为粒度的声明式任务定义
  • 配置字段模型设计:列规范、主键、同步策略的声明式表达
  • 配置注入机制:从声明式配置到运行时参数的完整链路

12.1 任务声明模型:业务域与数据实体粒度

Ch 11 讲了配置驱动的理念——"运行时配置存做什么,部署配置存在哪跑"。这一章把"运行时配置"拆开来看:任务怎么声明、配置字段怎么组织、配置怎么注入到运行时。

核心理念是"声明式"——配置描述"要什么结果"(我要从 PostgreSQL 拉取处方数据、增量同步、写到 enriched_sci schema),而非"怎么做步骤"(先连接数据库、再执行 SELECT、再转换格式、再写入 S3)。声明式配置把"加数据源"变成"加一条配置",不是"写一套代码"。

平台的每一个数据任务,都以 "业务域(domain)+ 数据实体(entity)" 为粒度声明。这是配置驱动架构的核心单元。

%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
flowchart TB
    subgraph 任务声明["任务声明 = domain + entity"]
        DOMAIN@{ icon: "codicon:organization", form: "rounded", label: "业务域 domain<br/>如:sci / retail / ma", pos: "b", h: 40 }
        ENTITY@{ icon: "codicon:database", form: "rounded", label: "数据实体 entity<br/>如:hospital_master / prescription_fact", pos: "b", h: 40 }
    end

    DOMAIN --> CONFIG@{ icon: "codicon:file-binary", form: "rounded", label: "一份任务配置 JSON", pos: "b", h: 40 }
    ENTITY --> CONFIG

    CONFIG --> RUNTIME@{ icon: "codicon:rocket", form: "rounded", label: "驱动完整的 ETL 流程<br/>摄取→标准化→加工→入仓", pos: "b", h: 40 }

classDef bpProcess  fill:#edf5ff,stroke:#0f62fe,stroke-width:2px,color:#161616
classDef bpData     fill:#d9fbfb,stroke:#007d79,stroke-width:2px,color:#161616
classDef bpInfo     fill:#f6f2ff,stroke:#8a3ffc,stroke-width:2px,color:#161616

class DOMAIN,ENTITY bpInfo
class CONFIG bpData
class RUNTIME bpProcess

linkStyle default stroke:#697077,stroke-width:2px

图 12-1 任务声明模型:业务域与数据实体粒度

为什么选 domain + entity 粒度

粒度选项 优势 劣势 是否采用
按数据源 与源系统对应清晰 一个源含多个实体,粒度太粗
按表 细粒度控制 缺少业务域归属,难治理
domain + entity 业务域隔离 + 实体级控制 需要前期定义好域与实体
按项目 与项目管理一致 技术视角错位

表 12-1 为什么选 domain + entity 粒度

domain + entity 粒度的好处是:业务域提供治理边界(权限/命名/调度隔离),数据实体提供操作单元(每个实体一条配置、一个独立流程)

这个粒度选择不是我一开始就想清楚的,而是试错过来的。最初我按"数据源"粒度建任务——一个 SFTP 源一个任务配置。但很快发现问题:一个 SFTP 源里可能有多个文件(医院主数据、处方数据、销量数据),它们的目标表、加载策略、脱敏规则都不同——按源粒度没法差异化。我又试了"按表"粒度——每张表一个配置,结果配置数量爆炸(20000+ 张表 = 20000+ 份配置),且没有"业务域归属"——权限按表授予太细,没法按域批量管理。最后我发现"domain + entity"是甜点——domain 做治理边界(按域授权、按域命名、按域调度隔离),entity 做操作单元(每个实体独立配置和流程)。粒度选择本质是"治理"和"操作"的平衡——太粗没法差异化,太细没法治理。

引申

domain + entity 的粒度选择,本质是在"治理粒度"和"操作粒度"之间找平衡。太粗(按数据源)控制不了细节;太细(按字段)配置会爆炸。domain 管"边界",entity 管"操作",这是实践中验证过的甜点。


12.2 配置字段模型设计:列规范、主键、同步策略的声明式表达

每份任务配置是一个 JSON 文档,声明"这个 entity 怎么摄取、怎么加工、怎么入仓"。以下是配置字段模型的核心设计(示意结构,非真实参数名):

%%{init: {'theme':'base','themeVariables':{'mindmapRootColor':'#0f62fe','mindmapTextColor':'#ffffff','mindmapMainColor':'#1d3649','mindmapSecondaryColor':'#393939','mindmapLineColor':'#697077'}}}%%
mindmap
  root((配置字段模型))
    源信息组
      source_type / source_connection
      source_query / file_pattern
    目标信息组
      target_schema / target_table
      load_mode / merge_strategy
    列规范组
      column_mappings
      primary_keys / partition_keys
    治理组
      quality_rules / masking_rules
      notification_channels

图 12-2 配置字段模型设计:列规范、主键、同步策略的声明式表达

字段组 核心字段 作用
源信息组 源类型、源连接信息、查询语句/文件模式 告诉引擎"数据从哪来、怎么取"
目标信息组 目标 schema/table、加载模式、 合并策略 告诉引擎"数据到哪去、怎么写"
列规范组 列映射、主键、分区键 告诉引擎"字段怎么映射、怎么去重"
治理组 质量规则、脱敏规则、通知渠道 告诉引擎"怎么校验、怎么脱敏、出问题通知谁"

表 12-2 配置字段模型设计:列规范、主键、同步策略的声明式表达

声明式表达的价值

配置是声明式的——它描述"要什么结果",而非"怎么做步骤":

# 声明式配置示意(简化)
{
  "domain": "sci",
  "entity": "prescription_fact",
  "source": {
    "type": "jdbc",
    "connection": "auroracdp/mssql/source-db",
    "load_mode": "incremental",
    "watermark_column": "updated_at"
  },
  "target": {
    "schema": "enriched_sci",
    "table": "prescription_fact",
    "merge_strategy": "upsert_by_key"
  },
  "columns": {
    "primary_keys": ["prescription_id"],
    "mappings": [...]
  },
  "governance": {
    "quality_rules": [...],
    "masking_rules": [...]
  }
}

Trade-off

声明式配置的好处是"加数据源 = 加配置",零代码。代价是配置字段模型的设计需要前期投入——字段太少不够用,太多则复杂度失控。我们的经验是:从最小可用集开始,按实际需求迭代扩展,而不是一开始就设计"完美"的字段集。

我从命令式到声明式的转变,是被企业征信的维护成本逼出来的。企业征信时每个 ETL 是命令式脚本——connect(db) → execute(sql) → transform(df) → write(target),逻辑写死在代码里。最初很灵活(想怎么写怎么写),但到第十个数据源时,十个脚本有十种写法,改一个通用逻辑(比如加质量校验)要改十处。到 Aurora 我把所有 ETL 逻辑抽象成"通用引擎",配置只声明"要什么"(源/目标/映射/策略),引擎按配置执行。这个转变的转折点是第三个月——有个新数据源接入,开发者只写了一条配置 JSON,没写一行代码,ETL 就跑通了。那一刻我才真正感受到声明式的分量:加数据源从"写代码"变成了"填表"。当然,代价是前期设计通用引擎投入了两个月——但这两个月换来了后续两年加几十个数据源零代码的效率,投入产出比极高。


12.3 配置注入机制:从声明到运行时参数

%%{init: {'theme':'base','themeVariables':{'primaryColor':'#edf5ff','primaryTextColor':'#161616','primaryBorderColor':'#0f62fe','lineColor':'#697077','secondaryColor':'#d9fbfb','tertiaryColor':'#f2f4f8','fontSize':'14px'}}}%%
sequenceDiagram
    participant C as 配置存储(DynamoDB)
    participant L as Lambda(参数处理器)
    participant SF as Step Functions
    participant G as Glue Job(通用引擎)

    L->>C: 按 domain+entity 读取配置
    activate C
    C-->>L: 返回配置 JSON
    deactivate C
    activate L
    L->>L: 校验配置完整性
    L->>L: 解析为运行时参数
    L->>SF: 触发状态机(注入参数)
    activate SF
    SF->>G: 启动 Glue Job(传递参数)
    activate G
    G->>G: 通用引擎按参数执行 ETL
    G->>C: 回写执行状态
    activate C
    deactivate C
    deactivate G
    deactivate SF
    deactivate L

图 12-3 配置注入机制:从声明到运行时参数

通用引擎的设计原则

Glue Job 是一个通用引擎——它不包含任何特定数据源的业务逻辑,所有行为由注入的配置参数决定:

设计原则 说明
配置即行为 引擎读什么源、怎么转换、写到哪,全由配置决定
零硬编码 不在代码中写死任何表名、字段名、连接串
** 分支路由** 引擎根据配置中的源类型,路由到对应的连接器分支
统一回写 无论哪个分支,执行状态都回写到统一的元数据表

表 12-3 通用引擎的设计原则

这四条原则里,"零硬编码"是最难做到的。我在第一版引擎里就没做到——虽然大部分逻辑是配置驱动的,但有些"特殊情况"被我硬编码了(比如某个数据源的字段名要做特殊转换)。结果半年后那个数据源下线了,硬编码逻辑成了"死代码",新开发者看不懂"为什么要特殊处理这个源"——技术债。第二版我严格清理了所有硬编码,特殊逻辑全部提取为配置项(如 column_transformations 字段)。零硬编码不是"尽量做到",而是"一条都不许有"——有一条例外,就会有两条、三条,最后引擎退化回"配置+硬编码"的混合体。

"分支路由"这条原则是连接器框架(Ch 13)的核心——引擎根据配置中的 source_type(jdbc/sftp/api/saas/mail)路由到对应的连接器分支。每个分支只处理"该类源的特有逻辑"(如 JDBC 分支处理连接池、SFTP 分支处理文件下载),公共逻辑(如写 S3、回写状态)在引擎主干。这个"主干+分支"的结构让加新连接器类型时只需加一个分支,不用动主干——扩展点收敛在分支,稳定性收敛在主干。

引申

通用引擎 + 声明式配置 = 数据工程的"编译器"模式。配置是"源代码"(声明意图),引擎是"编译器"(执行意图)。这种分离让引擎可以独立优化和升级,而不影响业务配置。这也是为什么我们能从框架 v1 平滑演进——引擎换了,配置不用动。


本章小结

  • 任务以 domain + entity 粒度声明:domain 提供治理边界,entity 提供操作单元
  • 配置字段模型分四组:源信息 / 目标信息 / 列规范 / 治理——声明式表达"要什么结果"
  • 配置注入链路:DynamoDB → Lambda 参数处理 → Step Functions → Glue 通用引擎
  • 通用引擎遵循"配置即行为、零硬编码、分支路由、统一回写"原则——这是配置驱动架构可持续演进的根基

下一章

Ch 13 连接器框架总览 —— 任务模型清楚了,接下来看连接器框架如何用"统一入口 + 源系统路由"设计实现五类连接器。

评论