全面迁移到WDL

背景

在构建生物信息分析流程时,工作流语言的选择至关重要。我最初选择了 Nextflow,毕竟它是主流选择,社区活跃、文档丰富。但随着流程复杂度的增加,Nextflow 的语法让我越来越难以忍受。

为什么放弃 Nextflow

Nextflow 基于 Groovy,语法灵活但同时也意味着——过于灵活。同一个逻辑可以写出 N 种风格,阅读他人写的流程常常是一种折磨:

// 风格1
process align {
    input:
    tuple val(sample_id), path(reads)
    output:
    tuple val(sample_id), path("*.bam")
    script:
    """
    bwa mem -t 16 ref.fa ${reads} | samtools view -bS - > ${sample_id}.bam
    """
}

// 风格2:各种花式写法
align = {
    exec "bwa mem -t 16 $ref $reads > $output.bam"
}

更让人头疼的是,Nextflow 的 DSL2 虽然引入了模块化,但同时也带来了更多的语法糖和隐式行为。调试一个复杂流程时,往往需要在多层抽象中追踪数据流向,心智负担很重。

WDL:所见即所得

相比之下,WDL(Workflow Description Language)给我的感觉是——所见即所得

version 1.2

workflow my_workflow {
    input {
        File reference
        File reads
    }
    
    call align {
        input:
            reference = reference,
            reads = reads
    }
}

task align {
    input {
        File reference
        File reads
    }
    
    command <<<
        bwa mem -t 16 ${reference} ${reads} | samtools view -bS - > output.bam
    >>>
    
    output {
        File bam = "output.bam"
    }
}

WDL 1.2 版本之后,还支持了文件夹作为参数导入,这在处理多文件输入时非常方便:

input {
    Directory reference_dir  # 直接传入文件夹
}

运行时:miniwdl vs Cromwell

WDL 的官方运行时是 Cromwell(由 Broad Institute 开发)。但 Cromwell 太重了:

  • 依赖 Java
  • 配置复杂
  • 资源占用大
  • 主要面向大规模集群调度

对于中小规模场景,miniwdl 是一个轻量的替代方案:

特性 Cromwell miniwdl
语言 Java Python
依赖 轻(纯Python)
启动速度 秒级
适用场景 大规模集群 单机/小集群
学习曲线 陡峭 平缓

开发效率对比:WDL + miniwdl 的组合,开发效率比 Nextflow 高很多。语法清晰、调试方便、错误信息直观。

美中不足

miniwdl 并非完美。最大的问题是:没有原生的 FastAPI 接口

如果想要构建一个 Web 服务来管理流程,需要自行开发。这其实也是 miniwdl 定位决定的——它是一个命令行工具,而非 Web 服务框架。

我的解决方案:Server + Agent 架构

既然没有现成的,那就自己造轮子。我的设计方案如下:

┌─────────────────────────────────────────────────────────┐
│                      Server 端                          │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐      │
│  │   Web UI    │  │   FastAPI   │  │  Database   │      │
│  └─────────────┘  └─────────────┘  └─────────────┘      │
└─────────────────────────────────────────────────────────┘
                           │ HTTP (主动推送)
┌─────────────────────────────────────────────────────────┐
│               Agent 端(部署在计算实例上)                │
│  ┌─────────────────────────────────────────────────┐    │
│  │  定时任务:每分钟抓取 miniwdl 状态 → 推送给 Server  │    │
│  └─────────────────────────────────────────────────┘    │
│  ┌─────────────┐                                      │
│  │   miniwdl   │                                      │
│  └─────────────┘                                      │
└─────────────────────────────────────────────────────────┘

核心设计

  1. Agent 主动推送:Agent 每分钟抓取一次 miniwdl 的运行状态,主动推送给 Server
  2. 无需开放端口:计算实例不需要监听任何端口,安全风险更低
  3. 无需知道实例 IP:Server 不需要维护实例列表,Agent 主动上报身份
  4. 实时状态同步:Server 端可以实时掌握所有计算实例的流程动态

工作流程

Agent 启动
检测本地 miniwdl 运行状态
POST /report → Server
    {
        "agent_id": "instance-001",
        "workflow": "wes-analysis",
        "status": "running",
        "progress": "45%",
        "tasks": [...]
    }
Server 更新数据库
Web UI 展示最新状态

优势

特性 传统方案 本方案
实例需要开放端口
Server 需要知道实例 IP
防火墙配置 复杂 无需配置
实例动态上下线 需要注册/注销 自动处理
网络穿透问题 可能存在

小结

从 Nextflow 迁移到 WDL,是一个"苦尽甘来"的过程:

  • 语法清晰:WDL 的声明式风格让流程逻辑一目了然
  • 开发效率高:所见即所得,调试方便
  • 运行时轻量:miniwdl 够用且足够轻
  • 可控性强:没有现成的 Web 接口,那就自己造一个

Server + Agent 的架构设计,本质上是一个控制反转:让计算实例主动汇报,而不是被动等待查询。这在云环境下尤其实用——实例可以随时创建、随时销毁,Server 无需关心实例细节,只需要接收汇报即可。

接下来的工作就是把这个架构实现出来,让 miniwdl 也能拥有企业级的流程监控能力。