Feature Chain — 用一个 operationId 追踪整个全栈

要修改一个功能需要改动哪些文件?输入一个 operationId,答案就出现。


问题

在全栈应用中,“一个功能"不在一个文件里。

假设你要修改一个叫"执行工作流(ExecuteWorkflow)“的功能。这个功能存在于 API 规范、服务逻辑、数据库 Schema、授权策略、状态转移图、外部函数调用、测试场景和前端组件中。

过去了解整个范围的方式有两种:

  1. 跑几十次 grep
  2. 手动追踪代码

两者都慢,两者都会遗漏。尤其是跨层引用——从 API 规范到服务,从服务到 DB Schema,从服务到授权策略——人类几乎不可能完整追踪。

把修改交给 AI 时更严重。在 200 个端点规模的代码库中,AI 无法把握全部上下文。上下文崩塌后模式漂移,第 201 个功能的成本变成第 21 个的 10 倍。这就是 vibe coding 撞上的墙。

但如果每层的源码都以符号方式引用其他层,只需沿着这些引用就能自动显示完整范围。


什么是 Feature Chain

Feature Chain 是与某个 API 功能(operationId)相连的所有源节点的集合。

以一个 operationId 为起点,沿着源文件之间的符号引用,一次性输出所有相关文件和行号。几十次 grep 被一条命令取代。

yongol chain ExecuteWorkflow specs/
── Feature Chain: ExecuteWorkflow ──

  OpenAPI    api/openapi.yaml                         POST /workflows/{id}/execute
  SSaC       service/workflow/execute_workflow.ssac   @get @empty @auth @state @call @publish @response
  DDL        db/workflows.sql                         CREATE TABLE workflows
  DDL        db/execution_logs.sql                    CREATE TABLE execution_logs
  Rego       policy/authz.rego                        resource: workflow
  StateDiag  states/workflow.md                       diagram: workflow → ExecuteWorkflow
  FuncSpec   func/billing/check_credits.go            @func billing.CheckCredits
  FuncSpec   func/billing/deduct_credit.go            @func billing.DeductCredit
  FuncSpec   func/worker/process_actions.go           @func worker.ProcessActions
  FuncSpec   func/webhook/deliver.go                  @func webhook.Deliver
  Hurl       tests/scenario-happy-path.hurl           scenario: scenario-happy-path.hurl

一个功能的完整结构在一个屏幕内呈现。未连接的层不会输出。


为什么能做到

这不是魔法。能做到是因为源码之间已经在互相引用。

在 yongol 框架中,各 SSOT(Single Source of Truth)层以符号方式指向其他层:

  • SSaC@get Model.Method → DDL 表
  • SSaC@auth action resource → Rego 授权策略
  • SSaC@state diagramID → Mermaid 状态图
  • SSaC@call pkg.Func → FuncSpec 实现
  • SSaC@publish "topic" → 订阅同一 topic 的函数
  • OpenAPI 的 operationId → SSaC 文件名
  • Hurl 场景 → OpenAPI 端点
  • React TSXapiClient.<op>() → OpenAPI operationId

这些引用最初是为 yongol validate 设计的——在编译前交叉校验 9 个 SSOT 一致性的阶段。因为 validate 已经在解析这些引用,复用同一基础设施后,feature chain 的提取就变成图遍历。


遍历路径

以 operationId 为起点,引用图会这样展开:

operationId (起点)
├── OpenAPI → path + method
├── SSaC → 服务函数文件
│   ├── @get → DDL 表
│   ├── @auth → Rego 策略规则
│   ├── @state → Mermaid stateDiagram 转移
│   ├── @call → FuncSpec 实现
│   └── @publish → 队列订阅者
├── Hurl → 引用 operationId 的测试场景
└── React TSX → 通过 apiClient 调用 endpoint 的前端文件

每个分支只沿一步符号引用前进。这是广度优先而非深度优先的遍历。一个功能在哪些层留下痕迹,全部显现。


它改变了什么

修改范围的把握

“要修这个功能得动哪里?”

为了回答这个问题我们读代码、跑 grep、问同事。Feature Chain 把这个问题替换成一条命令。只要符号引用存在,就不会有文件漏掉。

AI 驱动的代码修改

把功能修改交给 AI 时最难的是"告诉它修改范围”。必须把相关文件全部放进上下文窗口 AI 才能正确修改,而判断哪些文件相关是人做的事。

有了 Feature Chain 就不一样。只要给一个 operationId,整个修改范围就会自动识别出来。交给 AI 的上下文准备得毫无遗漏。9 个 SSOT 各自都是声明式规范——比生成的 Go 代码压缩得多——整个 chain 塞进上下文窗口仍有余裕。

代码审查

审查 PR 时,“既然改了这个功能,那个文件是不是也该变?“只需与 chain 对照就能立即验证。跨层不一致由结构代替人来捕捉。

上手引导

新加入的开发者说"我想知道 ExecuteWorkflow 怎么运作"时,给他看一个 Feature Chain 就好。从 API 到 DB,从授权策略到测试场景——一个功能的完整地形一目了然。


为什么选 operationId

起点选什么很重要。Feature Chain 选的是 operationId。

理由很简单。在全栈应用中**功能的单位就是 API 端点。**用户按按钮后,API 被调用,API 执行服务逻辑,读 DB,检查授权,转移状态。整个流程的起点就是 operationId。

operationId 已经在 OpenAPI 规范里定义好了,是团队共享的名字。说"要修改 ExecuteWorkflow"后端工程师、前端工程师、QA 都会想到同一样东西。Feature Chain 的设计就是以这个通用名字为贯穿整个栈的主线。

yongol 将此表达为**“operationId 是基石(keystone)"。**一个 PascalCase 标识符把 9 个层物理地绑在一起。


前提条件

Feature Chain 要能运作有前提:

  1. 源之间必须以符号方式互相引用。 SSaC 的 @get@auth@state@call@publish 指令必须明确指向其他 SSOT。如果引用是隐式的——例如 DB 表名只以字符串形式存在于代码中——就无法追踪。

  2. 必须通过 yongol validate Feature Chain 沿符号引用前进,如果引用无效,结果就会错。validate 保证引用的一致性,Feature Chain 在其上遍历。

这就是为什么 Feature Chain 不是通用工具。无法应用到任意项目。它只在源间引用经过设计的项目中运作——像 yongol 这样 SSOT 显式互相指向的结构。


未来:GEUL + SILK

当前 Feature Chain 遍历时依次调用各 SSOT 解析器。调用 SSaC 解析器、DDL 解析器、Rego 解析器、Mermaid 解析器、Hurl 解析器、TSX 解析器,然后组合结果。

当所有 SSOT 被转换成 GEUL 图后,Feature Chain 就变成**单一索引查询。**借助 SILK 的 SIDX 位与运算,可以一次查询就提取出连接到 operationId 的所有节点。

从按解析器遍历变成图查询。Feature Chain 的接口不变,内部却发生根本改变。


总结

在全栈应用中,“一个功能"不在一个文件里。它横跨多层,这些层互相引用。Feature Chain 沿着这些引用,自动提取一个功能的完整范围。

输入一个 operationId 就能从 API 规范到 DB Schema,从授权策略到测试场景,从函数实现到前端组件——把几十次 grep 换成一条命令。给 AI 的是无遗漏的上下文,给人的是"轨道上的自由”。

代码:github.com/park-jun-woo/yongol