3、层级结构设计:任务层级(项目/阶段/任务/子任务)、层级ID设计、父子关系映射

聊到跨层级任务依赖,第一个要解决的问题就是——任务到底该怎么分层?

我见过不少团队,一开始觉得「任务就是任务嘛,分那么细干嘛」。结果项目一跑起来,依赖关系乱成一锅粥。说白了,没有清晰的层级结构,你根本没法精确控制执行顺序。

3.1 四层任务模型:项目 → 阶段 → 任务 → 子任务

我个人习惯把任务拆成四个层级。你想想看,一个大型软件项目,从立项到上线,中间要经历多少环节?如果所有事情都平铺在一个列表里,那调度引擎根本没法做。

这四层分别是:

  • 项目(Project):最高层级,代表一个完整的交付物。比如「电商平台V3.0上线」。
  • 阶段(Phase):项目生命周期中的大阶段。比如「需求评审」、「开发」、「测试」、「部署」。
  • 任务(Task):阶段内的具体工作单元。比如「用户登录模块开发」。
  • 子任务(Subtask):任务的最小可执行单元。比如「编写登录接口」、「编写前端登录页面」。

核心原则:依赖关系只允许在同一层级或相邻层级之间建立。跨两层以上的依赖,必须通过中间层透传。这是避免循环依赖和调度混乱的关键。

我在项目中遇到过有人试图让「子任务A」直接依赖「项目B」的情况。嗯,这种设计一旦规模上来,调度器直接崩溃。所以,层级边界必须守住。

3.2 层级ID设计:一眼看出任务在哪一层

层级结构有了,怎么在代码里标识它?我建议用层级ID。这玩意儿就像任务的「身份证号」,看一眼就知道它在哪一层、父节点是谁。

常用的设计有两种:

方案 示例 优点 缺点
前缀式ID PRJ-001 / PH-001-01 / TSK-001-01-01 / SUB-001-01-01-01 可读性强,一眼识别层级 ID长度随层级增长,存储稍大
数字路径式ID 1 / 1.1 / 1.1.1 / 1.1.1.1 排序方便,父子关系天然体现 重编号时维护成本高

我个人更倾向前缀式ID。为什么?因为可读性太重要了。你在日志里看到 TSK-001-01-01,马上就知道这是个任务,属于阶段01、项目001。如果是纯数字路径,你还得去查表才知道它是什么层级。

小技巧:前缀式ID的层级码建议用两位数字(如01、02),不要用一位。这样未来在中间插入新节点时,不会打乱排序。我曾经吃过这个亏,后来全改成了两位。

3.3 父子关系映射:怎么存、怎么查

层级ID只是表象,真正的核心是父子关系映射。说白了,就是怎么在数据库里记录「谁是谁的爸爸」。

我常用的方案是邻接表(Adjacency List),也就是每个任务记录一个 parent_id。结构简单,查询直接。

-- 任务表结构示例
CREATE TABLE tasks (
    id VARCHAR(32) PRIMARY KEY,        -- 层级ID,如 'TSK-001-01-01'
    name VARCHAR(128),                 -- 任务名称
    level ENUM('project','phase','task','subtask'),  -- 层级枚举
    parent_id VARCHAR(32),             -- 父任务ID,根节点为NULL
    sort_order INT,                    -- 同级排序
    created_at DATETIME
);

-- 查询某个项目的所有子任务(递归)
WITH RECURSIVE task_tree AS (
    SELECT id, name, level, parent_id, 1 AS depth
    FROM tasks
    WHERE id = 'PRJ-001'
    UNION ALL
    SELECT t.id, t.name, t.level, t.parent_id, tt.depth + 1
    FROM tasks t
    INNER JOIN task_tree tt ON t.parent_id = tt.id
)
SELECT * FROM task_tree ORDER BY depth, sort_order;

这段SQL用了递归CTE,能一次性把整棵树拉出来。我在项目中遇到过性能问题——当任务数超过10万时,递归查询会变慢。这时候我建议加一个路径枚举(Path Enumeration)字段,比如 path = 'PRJ-001/PH-001-01/TSK-001-01-01',用 LIKE 'PRJ-001/%' 来查,速度能快一个数量级。

避坑指南:千万不要在应用层用循环去递归查数据库!我曾经见过一个项目,每次展开任务树都要发N次SQL查询,页面加载直接卡死。要么用数据库递归CTE,要么用路径枚举,要么在内存里构建树结构。总之,别让数据库做N+1查询。

3.4 层级结构图:一张图看懂全局

说了这么多,不如一张图来得直观。下面这张SVG图展示了完整的四层任务层级、ID设计以及父子关系映射。

任务层级结构与ID设计 项目 PRJ-001 阶段 PH-001-01 阶段 PH-001-02 阶段 PH-001-03 任务 TSK-001-01-01 任务 TSK-001-01-02 任务 TSK-001-02-01 任务 TSK-001-03-01 子任务 SUB-001-01-01-01 子任务 SUB-001-01-01-02 子任务 SUB-001-01-02-01 子任务 SUB-001-02-01-01 子任务 SUB-001-03-01-01 父子关系映射规则 每个子节点通过 parent_id 指向父节点ID | 同级节点通过 sort_order 控制顺序 | 依赖只允许同层或相邻层

3.5 层级设计中的常见陷阱

最后,我总结几个实际项目中踩过的坑:

  1. 层级过深:有人把子任务下面又分了「子子任务」。我建议最多四层,再深就说明粒度太细了,应该合并。
  2. 跨层依赖:子任务直接依赖另一个项目的阶段。这种依赖必须通过父任务透传,否则调度逻辑会乱。
  3. ID重复:不同项目用了相同的任务编号。前缀式ID天然避免了这个问题,但如果你用自增ID,一定要加上项目前缀。
  4. 排序混乱:同级任务没有排序字段,全靠创建时间。结果就是每次展示顺序都不一样。加个 sort_order 字段,一劳永逸。

我的习惯:在项目初期,我会先画一张层级结构图,跟团队确认好每一层的定义。别等到代码写了一半再来改层级,那代价太大了。