4. Nextflow项目骨架搭建

从 Snakemake 迁移到 Nextflow,第一步不是急着把规则逐条翻译成 process,而是先把工程骨架立起来。Snakefile 时代,一个文件往往就能承载全部逻辑:规则、输入输出、shell 命令、参数、资源声明都挤在一起。Nextflow 的工程组织方式不同,它鼓励把配置、流程编排、可复用模块和子工作流分层放置。骨架搭得合理,后续迁移会顺很多;骨架搭得随意,后面每加一个 process 都要回头改路径、改参数、改引用。

本节要完成的目标很明确:创建 nextflow.config、main.nf、modules 目录和 subworkflows 目录,并规划出迁移后的工程结构。下面先给出一个可直接落地的目录布局,再逐层说明每个文件或目录承担什么职责、里面应该写什么、以及如何验证骨架可用。

迁移后的工程结构总览

一个适合从 Snakemake 迁移过来的 Nextflow 项目,推荐采用如下结构。它既保留了 Nextflow 官方推荐的模块化习惯,也照顾了 Snakemake 用户对“规则文件”和“配置文件”的直觉。

my_pipeline/
├── main.nf
├── nextflow.config
├── modules/
│   ├── fastqc.nf
│   ├── trim_galore.nf
│   └── alignment/
│       └── bwa_mem.nf
├── subworkflows/
│   └── qc_trim_align.nf
├── conf/
│   ├── base.config
│   ├── test.config
│   └── local.config
├── bin/
│   └── summarize_stats.py
├── data/
│   └── samplesheet.csv
└── results/

这个结构里,main.nf 是入口,nextflow.config 是全局配置,modules/ 放最小可复用单元,subworkflows/ 放多个模块组合成的阶段性子流程,conf/ 放环境相关配置,bin/ 放自定义脚本,data/ 放样本表,results/ 是默认输出目录。下面这张图展示了各部分的依赖与调用关系。

Nextflow 迁移工程骨架与调用关系 main.nf 入口:定义 workflow、调用子流程 nextflow.config 全局参数、执行器、资源、容器 conf/ base / test / local 环境配置 subworkflows/ qc_trim_align.nf:阶段性子流程 modules/ fastqc / trim_galore / bwa_mem bin/ summarize_stats.py data/ samplesheet.csv results/ publishDir 输出目录 读取配置 include 调用 引用 调用脚本 读取样本 发布结果

nextflow.config:把散落在 Snakefile 里的参数收拢

Snakemake 中,线程、内存、容器、输出目录等常写在规则里,或者通过 config.yaml 传入。Nextflow 把这些统一收进 nextflow.config。骨架阶段不需要写全,但至少要把参数入口、执行器、默认资源和输出目录定下来。

// nextflow.config
params {
    samplesheet = "data/samplesheet.csv"
    outdir      = "results"
    fastqc      = true
    trimmer     = "trim_galore"
}

process {
    executor = 'local'
    cpus     = 2
    memory   = '4 GB'
    time     = '2h'
    errorStrategy = 'retry'
    maxRetries    = 2
}

docker {
    enabled = true
}

// 按环境加载额外配置
includeConfig 'conf/base.config'

这里有几个迁移时容易踩的点。第一,params 中的默认值只是兜底,实际运行时可以用 --samplesheet 覆盖,这与 Snakemake 的 --config 思路一致。第二,process 块中的资源是全局默认值,单个 process 可以覆盖,这相当于 Snakemake 中规则级 threads 和 resources 的默认继承。第三,includeConfig 让环境差异从主配置中分离,测试环境、本地环境和集群环境各用各的,避免把集群队列名写死在主配置里。

main.nf:只做编排,不写具体命令

main.nf 是流程入口,它的职责是定义 workflow、读取输入、调用子流程或模块,而不是把 shell 命令堆在里面。迁移初期,可以先把 Snakemake 的 rule all 对应成 Nextflow 的顶层 workflow,再把每个 rule 逐步下沉到 modules。

// main.nf
nextflow.enable.dsl = 2

include { QC_TRIM_ALIGN } from './subworkflows/qc_trim_align.nf'

workflow {
    samplesheet = file(params.samplesheet)
    QC_TRIM_ALIGN(samplesheet)
}

这段代码虽然短,但已经体现了 Nextflow 的核心组织方式:入口文件保持干净,真正的处理逻辑通过 include 引入。后续如果增加变异检测或报告生成,只需要在 main.nf 中再 include 一个子流程,而不必改动已有模块。

modules 与 subworkflows:从规则到可复用单元

modules/ 存放最小可复用单元,通常一个文件对应一个 process。以 fastqc.nf 为例,它应该只负责一件事:接收 reads,运行 FastQC,输出报告。

// modules/fastqc.nf
process FASTQC {
    tag "$sample_id"
    publishDir "${params.outdir}/fastqc", mode: 'copy'

    input:
    tuple val(sample_id), path(reads)

    output:
    path("${sample_id}_fastqc.html")
    path("${sample_id}_fastqc.zip")

    script:
    """
    fastqc -t ${task.cpus} ${reads}
    """
}

subworkflows/ 则把多个模块串成一个阶段。例如 qc_trim_align.nf 可以依次调用 FASTQC、TRIM_GALORE 和 BWA_MEM,把“质控—修剪—比对”这条链封装起来。这样 main.nf 只需要关心“我要跑哪个阶段”,而不需要知道阶段内部有几个 process、谁先谁后。

// subworkflows/qc_trim_align.nf
include { FASTQC } from '../modules/fastqc.nf'
include { TRIM_GALORE } from '../modules/trim_galore.nf'
include { BWA_MEM } from '../modules/alignment/bwa_mem.nf'

workflow QC_TRIM_ALIGN {
    take:
    samplesheet

    main:
    reads_ch = Channel.fromPath(samplesheet)
        .splitCsv(header: true)
        .map { row -> tuple(row.sample_id, file(row.fastq_1), file(row.fastq_2)) }

    FASTQC(reads_ch)
    TRIM_GALORE(reads_ch)
    BWA_MEM(TRIM_GALORE.out.trimmed_reads)
}

注意这里的 take 和 main 是子流程的输入声明与主体。迁移时,建议先把 Snakemake 中耦合较紧的规则按“阶段”分组,再决定哪些进 modules、哪些进 subworkflows。判断标准很简单:如果一个 process 只做一件事、可以被别的流程复用,就放 modules;如果它是一组固定搭配的阶段,就放 subworkflows。

骨架验证:先跑通空流程,再填内容

骨架搭好后,不要急着把所有规则搬进来。先用一个最小样本表跑通空流程,确认路径、配置和 include 都没有问题。样本表可以只有两列:sample_id 和 fastq_1。运行命令如下:

nextflow run main.nf -profile test --samplesheet data/samplesheet.csv

如果此时还没有写任何 process,可以把 main.nf 中的子流程调用暂时注释掉,只保留 workflow { println "skeleton ok" },确认 Nextflow 能识别配置和入口。然后再逐步取消注释,每加一个模块就运行一次 -stub-run,用桩运行检查通道连接和输入输出声明是否正确。这样做的价值在于:把“工程结构问题”和“业务逻辑问题”分开排查,避免后面出错时不知道是路径写错还是命令写错。

迁移初期建议保留 Snakemake 的目录结构作为对照,但不要直接把 Snakefile 放进 Nextflow 项目根目录。两者混放容易让 include 路径和输出目录互相干扰。可以先把旧项目放在 legacy/ 下,迁移完成后再删除。

工程结构规划中的几个决策点

第一,模块粒度。Snakemake 中一个 rule 可能同时做比对和排序,迁移到 Nextflow 时建议拆成两个 process,因为 Nextflow 的通道机制更适合“一个 process 一个明确输出”。第二,子流程边界。按生物学阶段划分通常比按工具划分更稳定,例如“质控与修剪”“比对与去重”“变异检测与注释”,这样即使更换工具,子流程接口也不用大改。第三,配置分层。nextflow.config 只放默认值和参数入口,环境差异放 conf/,通过 -profile 切换。第四,输出目录。统一用 params.outdir 控制,各 process 的 publishDir 只负责子目录,避免硬编码绝对路径。

完成本节后,你应该得到一个可以运行、可以扩展、职责清晰的 Nextflow 工程骨架。它不要求一次把所有规则迁移完,但要求每个目录和文件都有明确归属:配置进 config,编排进 main,最小单元进 modules,阶段组合进 subworkflows。后续迁移时,每翻译一个 Snakemake 规则,就把它放进对应的 module,再在 subworkflow 中接线,工程结构不会随着迁移进度而失控。