首页
/ Apache Airflow ADR-0006 解析:为什么 `@task.stub` 混合语言 DAG 不在 Code 视图中展示 Lang-SDK 源码

Apache Airflow ADR-0006 解析:为什么 `@task.stub` 混合语言 DAG 不在 Code 视图中展示 Lang-SDK 源码

2026-09-08 13:37:25作者:薛曦旖Francesca

导读

在 Apache Airflow 的 Lang-SDK(语言 SDK,当前落地为 Java SDK)架构中,用 @task.stub 声明的 Java/Go 任务与 Python 任务共存于同一个 DAG(即“混合语言 DAG”)。此时用户会在 Airflow UI 的 Code 页看到一份代码——本文依据仓库中的架构决策记录 ADR-0006airflow-core/adr/lang-sdk/ 目录)展开,说明一个看似“缺功能”实则是有意为之的设计结论:Airflow 不会在 UI 中展示混合语言 DAG 的 Lang-SDK 侧源码,Code 视图只显示包含 stub 声明的 Python DAG 文件。读完本文,你将理解这项“负面决策”背后的四条工程理由、被否决的三种备选方案(含完整的设计草图)、它对 DagCode 表结构与 /dagSources REST 契约的现状约束,以及它如何与纯 Java DAG 的源码打包机制(ADR-0003)相互独立、各司其职。

ADR-0006 的定位:记录一项“不做”的决策

ADR-0006 是一份 Architecture Decision Record(架构决策记录),状态为 Accepted(已接受)。它的特殊之处在于记录的是一个 negative decision(否定性决策)——正如 ADR 顶部注释所写:

这份 ADR 记录了一项否定决策。它存在的意义是:今后任何提议为 stub 支撑的 DAG 提供多文件/多语言源码展示的功能请求或 PR,都可以直接指向本文,而不必重新讨论设计。

仓库的 lang-sdk ADR 目录 统管“在 Airflow 中运行非 Python 任务”(AIP-108)所涉及的横切架构决策,包括协调器层、工作负载执行、打包方式,以及语言 SDK 触碰 Airflow 核心表面(DAG 解析、DagCode、REST API 与 UI)的方式。这些决策原本在 java-sdk/adr/,后经评审迁移到核心侧,因为它们约束的是核心接口,且适用于所有语言 SDK 而非仅 Java SDK。整个系列包括:

  • ADR-0001:Java SDK 与 Airflow 集成及协调器扩展点
  • ADR-0003:纯 Java DAG——构建期打包与代码可见性
  • ADR-0004:语言特定 DAG 文件解析
  • ADR-0005:协调器打包、模块布局与注册
  • ADR-0006:混合语言(@task.stub)DAG 不展示 Lang-SDK 源码
  • ADR-0007:跨语言边界的 TaskFlow 参数绑定

而仅针对单一 SDK 的决策仍留在对应 SDK 目录中(如 Go SDK 的 bundle 格式决策位于 go-sdk/adr/)。

背景:什么是混合语言(@task.stub)DAG

按 AIP-108 的当前范围,Java 任务以 @task.stub 操作符的形式声明在普通的 Python DAG 文件中:DAG 本体用 Python 定义,任务实现则位于随 bundle 一起分发的 Java-SDK 构建产物(JAR)里。这就是 ADR-0006 所称的 mixed-language Dag(混合语言 DAG)

一个典型的混合语言 DAG 如下(示例源自 ADR-0001):

@task()
def python_task_1():
    return "value_from_python_task_1"


@task.stub(queue="java")
def extract(): ...


@task.stub(queue="java")
def transform(): ...


@task()
def python_task_2(transformed):
    print(transformed)


@dag(dag_id="java_interface_example")
def simple_dag():
    python_task_1() >> extract() >> transform() >> python_task_2()

关键点在于:@task.stub 的声明不带任何 Python 实现,执行被委托给该任务 queue 所对应的协调器(coordinator)。从数据模型上,stub 是一个被标记的“非映射操作符”——在核心序列化层,操作符上存在 is_stub: bool = False 字段(见 baseoperator.py),序列化 DAG 时若操作符带 is_stub,则会调用 stub_arg_bindings.py 在 DAG 序列化期物化其有序位置参数绑定规格(build_arg_bindings),供远端运行时解码(见 serialized_objects.py)。

与之相对的是 纯 Java DAGADR-0003):DAG 本体用 Java 定义,协调器通过 Airflow-Java-SDK-Dag-Code manifest 属性解包内嵌的 .java 源码用于展示。该机制随纯 Java DAG 创作一并被移出了 AIP-108 的范围——当前 AIP-108 下 Java 只能以 stub 任务的形式参与 DAG,纯 Java DAG 留待后续(很可能在 AIP-85 稳定之后)再提。

曾被提出但被否决的设计草图

在 AIP-108 的测试与演示期间,用户提出:混合语言 DAG 的 UI Code 视图能否在包含 stub 声明的 Python 文件之外再展示 Java-SDK 侧的源码——例如在 Python 源码旁边加一个第二标签页。对应追踪问题在 ADR 中被记为 #67260(该问题最终以 won't-fix 关闭并指向本文)。

一份完整的设计草图当时被起草,以扩展 AIP-85 的 DagImporter 接口来实现,包含四个组成部分:

  1. StubDagImporter:继承自 Python importer 的子类,在解析后定位每个 stub DAG 在 bundle 中的支撑产物(artifact)并捕获其源码。
  2. get_source_code 变为“每个 DAG、多文件”语义:签名从现有形式扩展为 get_source_code(file_path, dag_id, *, bundle_path, bundle_name) -> dict[str, DagSource], 使定义在同一个 Python 文件中的多个 DAG 不会把彼此的 artifact 泄漏进各自的 Code 视图。
  3. 数据库 schema 变更:允许每个 DagVersion 对应多行 DagCode——移除 DagCode.dag_version_id 上的唯一约束,新增 source_file_namelanguage 与一个入口点标记列;artifact 摘要(digest)参与 dag_hash,使 JAR 重新构建后产生新的 DagVersion
  4. REST API 与 UI 变更GET /dagSources/{dag_id} 增加“文件”维度;Code 页增加文件标签栏,按文件标记语言,取代硬编码的 Python。

决策:不支持,Code 视图维持单文件现状

经过讨论,社区决定不推进上述方案。ADR-0006 的正式决策是:

我们不支持查看混合语言 DAG 的 Lang-SDK 侧源码。 对于包含由 Lang-SDK artifact 支撑的 @task.stub 任务的 Python DAG,Code 视图只展示 Python DAG 文件——即现有的“单源码、按 DagVersion”行为。

具体而言,以下内容均未实现

  • 一个能解析并捕获 stub 支撑 DAG 的 Lang-SDK artifact 源码的 StubDagImporter(或 CrossLangDagImporter);
  • get_source_code 的“每个 DAG、多文件”语义;
  • 每个 DagVersion 对应多行 DagCode,或 DagCode 上新增 source_file_name / language / 入口点标记列;
  • GET /dagSources/{dag_id} 上的文件维度,或针对该用例的多文件 Code 页;
  • 仅为“源码展示”目的而把 artifact 摘要盖到序列化 DAG 模型上(artifact 摘要是否应参与 DAG 版本化以保障执行正确性,是另一个问题,超出本 ADR 范围)。

这一现状在仓库核心代码中有清晰印证:

  • dag_code 表一行对应一个 DAG 版本:模型 dagcode.py 中,dag_code 表将 source_code 存为单列 Text(MySQL 上为 MEDIUMTEXT),并带 source_code_hash;其外键列 dag_version_id 声明了 unique=True(见 dagcode.py),即同一 DAG 版本至多一行代码。DagVersion 侧的回填关系 dag_code 同样以 uselist=False 建模(见 dag_version.py)。
  • 没有语言/文件名维度/dagSources API 的响应模型 DAGSourceResponse 只有 contentdag_idversion_numberdag_display_name 四个字段(见 dag_sources.py 数据模型),不存在 languagesource_file_name
  • REST 契约单文件:公共路由 GET /api/v2/dagSources/{dag_id}(见 dag_sources.py 路由)返回单份内容,支持 JSON 或纯文本两种 Accept。
  • UI 的 Code 页按 DAG 版本取单份源码:页面组件 Code.tsx 通过 useDagSourceServiceGetDagSource 拉取所选 DagVersion 对应的源码,配合 DagVersionSelect 做版本切换,用 Monaco Editor 展示单一文件——没有多文件标签栏。

另外,源码展示还受权限控制:无权限时路由返回 REDACTED - you do not have read permission on all Dags in the file(见 dag_sources.py 路由),并依赖 requires_access_dag(... access_entity=DagAccessEntity.CODE) 做鉴权。

为什么不做:四条工程理由

ADR-0006 给出了四点理由,按优先级逐条拆解如下。

1. 含 stub 任务的 DAG 并不是一种独立的 DAG 类别

stub 操作符只是普通 Python DAG 里的又一个操作符。一个 DAG 很可能在近期就会混合多种语言的任务,因此并不存在一个原则上可界定、有界的“另一侧文件”集合。如果围绕“特殊的 stub DAG”概念去设计 importer 接口,等于把一个人为的区分固化进 AIP-85 的 DagImporter 契约——为将来埋下一个需要长期维护的错误抽象。

2. 它最终会退化成在 dag_code 里存一棵目录树

要对 Java 侧展示任何“真正有用”的内容,都意味着不止一个文件——尤其 Java 代码习惯把每个任务拆到各自的小文件/类中。照此思路推演到底,就是把整棵目录树持久化到 dag_code 表——而这并非该表的职责。dag_code 的设计定位是“同步由调度器处理的 DAG 文件代码”,每次写入读取单份源码文件,并计算 md5 哈希(dag_source_hash,见 dagcode.py),其语义与“存储一整个源文件树”格格不入。

3. 源码视图在纯 Python 场景下本就是尽力而为(best-effort)

由工厂函数生成的 DAG,其 Code 视图展示的本来就是一个不含“真正定义”的文件。混合语言 DAG 只是这个已知、被接受的限制的又一实例,而非必须弥补的新缺口。换句话说,Airflow 的源码展示从未承诺“所见即真实定义”,因此混合语言场景并不构成回归。

4. 该问题是暂时性的,且将变得罕见

一旦 DAG 可以原生地用其他语言创作(即 ADR-0003 / ADR-0004 所述的纯 Java DAG,很可能在 AIP-85 稳定之后),在乎 UI 中查看 Lang-SDK 代码的作者可以直接编写原生 Lang-SDK DAG——其入口源码会经由普通的单文件路径作为该 DAG 的源码被展示。为一个持续缩小的边缘场景,把 DagImporter 接口、DagCode schema、版本化写入路径以及 REST/UI 契约全部拧成死结,是一笔糟糕的交易。

备选方案对比

ADR 记录了三个被评估的选项:

方案 结论 理由
完整提案(每 DAG 多文件 get_source_code + 每 DagVersion 多行 DagCode + REST/UI 变更) 拒绝 接口与 schema 复杂度、原子多行写入语义、artifact 哈希耦合进 dag_hash——全部只为罕见且暂时的用例
仅展示 Lang-SDK 入口文件(作为第二标签页) 拒绝 Java 代码往往分布在众多小文件中,仅展示入口价值有限,却仍需要几乎全部相同的 schema、API 与 UI 改动
维持现状——只展示 Python 文件 接受 见上节四条理由

结论与实际影响

对架构各层的影响:一切保持不变

  • AIP-85 的 DagImporter 接口、DagCode schema(每个 DagVersion 一行)、/dagSources API 契约以及 Code 页 UI 全部保持原样;
  • 无需为源码展示而构建、测试或维护跨语言源码对账(cross-language source reconciliation)、多行原子写入语义或由 artifact 哈希驱动的 DagVersion 更替。

对混合语言 DAG 用户意味着什么

使用混合语言 DAG 的用户,在 Code 视图中只会看到 Python stub 声明,要查看 Java 实现必须回到自己的仓库或构建产物。ADR 特别注明:Java SDK 的用户文档会显式声明这一行为,使其成为预期行为而非被上报的缺陷。

这与 Dag 版本化机制的配合也值得注意:每当 DAG 文件变化(内容或 fileloc 变化),dag_code 中的行会被写入或更新(write_code / update_source_code,见 dagcode.pydagcode.py);而每次解析会由 DagVersion.write_dag 递增 version_number(见 dag_version.py,配合 (dag_id, version_number) 唯一约束)。Code 页的版本选择器正是基于这套 dag_version 历史,让用户能回看某个版本的 Python 声明文件——但无论哪个版本,它都只对应一份 Python 源码。

与 ADR-0003 源码打包机制的关系:互不影响

ADR-0003 的源码打包机制(Airflow-Java-SDK-Dag-Code manifest 属性)不受本决策影响:如果未来某个后续提案重新启用纯 Java DAG 创作,该机制依然是展示原生创作的 Lang-SDK DAG 源码的方式。也就是说,Airflow 存在两条并行的源码可见性路径:

  • 混合语言 stub DAG(当前 AIP-108 范围):Code 视图 = Python 声明文件,Java 实现看仓库/构建产物;
  • 原生 Lang-SDK DAG(ADR-0003,暂移出范围):构建期将 .java 入口源码打进 JAR,协调器解包后用现有 DagCode 基础设施存储与展示。

实践建议与 FAQ

Q:我在 UI 中看不到我的 Java 任务实现,是出 Bug 了吗? A:不是。对含 @task.stub 的混合语言 DAG,这是 ADR-0006 明确的预期行为——Code 视图按设计只显示 Python DAG 文件。

Q:为什么不能用“再加一个标签页”这样简单的方案? A:Java 源码以多文件、每任务一类的形态存在,单入口文件价值有限,却几乎触发与完整方案等量的 schema/API/UI 改动。权衡后被否决。

Q:如果我希望在 Airflow UI 中看到 Lang-SDK 侧源码,该怎么办? A:两条路径:其一,在仓库中查看对应语言 SDK 的任务源码(构建产物 JAR 内的实现);其二,等待纯 Java(原生语言)DAG 创作能力在未来被重新纳入范围——届时该 DAG 的入口源码将通过普通的单文件路径展示。

Q:这个决策会锁死未来的演进吗? A:不会。作为负面决策记录,它的作用是让未来同类提议直接引用本文、避免重复论证;若 AIP-85 稳定后 AIP-108 范围再次扩展,纯语言 DAG 路径将天然解决源码可见性诉求。

进一步阅读

围绕本文涉及的源码展示与版本化链路,可在仓库中继续深入:

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391