距离上一次更新博客,已经过去两个多月了。
从五月初开始,我的人生方向有些失去坐标。差不多三个月里,状态一直不太好。颓废这个词放在这里并不过分,很多事情提不起劲,过去很确定的目标也突然失去了吸引力。
那段时间,我开始接触和学习一些心理学,也第一次认真去看自己的心理投射。
人很容易把没有被自己看见的需要、期待和担心投向外界,然后从别人的反应里确认自己。很多以为是在处理现实的问题,里面其实夹着没有被正视的恐惧:害怕不被认可,害怕自己不够好,害怕做错选择,也害怕某一天发现,过去坚持的方向并不适合自己。
以前我会用忙碌把这些东西压下去。写代码、学新技术、做复杂系统,都能带来一种很直接的确定感。只要事情一直往前推进,好像就不需要回答那些更难的问题。
这次没有躲过去。
回头看,我甚至觉得这是一种幸运。恐惧赶在三十岁以前被提上了日程,我还有时间去认识它,也有时间重新理解自己。如果它一直被埋着,可能要到更久以后,才会以更难处理的方式出现。
现在当然谈不上什么都想明白了,但至少开始知道,哪些选择来自自己,哪些选择只是为了获得评价;哪些焦虑来自现实,哪些焦虑来自比较。
这种变化也慢慢进入了工作。
过去很长一段时间里,我会下意识地从别人的评价里证明自己。系统做得够不够复杂,方案看起来够不够厉害,别人是否认可我的能力,这些东西多少都会影响我的判断。
现在更愿意让工作回到工作本身。
一个需求值不值得做,一套架构是否真的降低了维护成本,一个功能有没有解决用户的问题,这些事实比外界评价更重要。也不需要再和谁比较,每个人有自己的节奏、经历和方向。
我正在尝试和自己成为朋友。允许自己偶尔停下来,允许状态不好,也允许过去的一些判断被推翻。等那些证明、比较和焦虑慢慢退下去以后,工作反而变得更清楚了。
这种变化,最近也落在了 Knowledge Hub 上。
起点是一个很具体的限流需求
这次原本要做的是限流。
KH 里有一些昂贵操作,比如文档解析、索引、检索、Agent 执行以及开放 API 调用。它们一旦被恶意请求或者无意并发拖住,后端很容易受到影响。
当时的计划很明确:给昂贵操作设置严格的 Rate Limit,提供临时封禁用户的 Killswitch,再通过响应 Header 告诉客户端剩余额度和重试时间。
这类需求很容易直接落成一个中间件,再配几张 Redis Key 和管理开关。
准备往下做的时候,我突然问了一个问题:
Knowledge Hub 现在臃肿吗?维护起来到底有多难?
这个问题让限流暂时停了下来。
系统已经走到了需要回头看的位置
KH 已经不再是一套简单的 RAG 服务。
它现在有 Project、Dataset、Document、VFS、GitSync、权限、资产、RAG、Agent、Chat、Workspace、Admin、审计和后台 Worker。产品能力一直在增长,代码也很自然地跟着长了起来。
当前后端有大约 6.4 万行 Go,三个前端加起来超过 5 万行 TypeScript。系统注册了 123 条模块路由,docs 目录里有 282 份 Markdown。
数字本身没有否定这个项目。KH 承担的功能本来就复杂,企业知识系统也不可能只靠几个 CRUD 接口撑起来。
真正影响维护的是职责开始缠在一起。
document.Service 已经有 61 个方法,从上传、解析、分片、修订、治理,一直管到 Git、VFS、ACL 和资产。project.Service 有 41 个方法,项目生命周期、目录树、权限和摘要都挤在同一份契约里。GitSync 的核心 Service 超过 2200 行,RAG 包接近 1.1 万行。
一些关键模块还缺少直接测试。与此同时,两份配置示例已经出现冲突,部分旧字段会被 Viper 静默忽略;文档里也残留着大量过期 Phase、旧接口和已经变化的系统描述。
KH 的业务复杂度有真实来源,工程里积累的偶然复杂度也到了该处理的时候。
一开始想的是拆,后来讨论成了收拢
最初的重构思路是:拆开 Document、Project、GitSync 和 RAG,把大 Service 改成一组小接口。
这个方向没有错,但它只处理了系统内部。
讨论继续往下走时,一个更大的问题冒了出来:
整个服务能不能收拢?
如果只拆内部实现,外部依然散落着 Project、Document、Dataset、Content、Workspace 等多套入口,三个前端继续维护各自的 API、鉴权和流式处理,系统仍然很难形成一个稳定的产品边界。
最终收敛出来的架构大概是这样:
1 | 产品入口 |
这里的收拢发生在产品入口、API 命名和应用能力层。内部依然保留专业组件,每个组件只处理自己真正拥有的规则。
路由先收拢,代码晚一点再动
这次讨论里,我最认可的一个修正,是把纯路由映射放到内部重构之前。
新的 Workspace API 会按照资源建立二级结构:
1 | /api/workspace/projects/* |
第一阶段不会重写 Handler。新旧路径直接指向同一个处理函数:
1 | /api/projects/list |
这一步只改变入口名称,业务行为保持一致。
看起来很简单,真正危险的地方在中间件。
KH 的路由没有使用完全相同的 Guard。普通 Project 和 Document 接口通常经过 JWT、Isolation、Governance;Audit 还会增加 PortalGuard 和 AdminMiddleware;Asset 与 Git Image 又有独立挂载的 GET 路由。
因此,重构前必须生成一份 Route Manifest。每条路由都要记录 Method、完整 Path、Handler、Guard、中间件顺序、RequiredPermission、响应格式、Header、SSE 或文件流语义,以及当前有哪些客户端在调用。
执行顺序也因此变得很清楚:
1 | 行为基线与 Route Manifest |
代码暂时没变,系统的产品边界已经可以先稳定下来。
Retrieval 应该被共享,Ingestion 跟着内容走
RAG 的归属也经历了一次调整。
最初的想法是把整个 RAG 都放进 Intelligence Runtime。继续推演后发现,Workspace Search 和 Chat/Agent 都会消费检索能力。Workspace 如果为了搜索而依赖 Intelligence Runtime,边界会再次绕起来。
最终的划分是:
1 | Workspace Ingestion -> Index Writer -> Search Index |
文档摄取、切片、Embedding、索引状态归 Workspace,因为它们跟着内容生命周期发生。
Retrieval 成为共享查询能力,负责语义召回、关键词召回、混合融合、Rerank、路径过滤、ACL 过滤和 Evidence 组装。Chat 和 Agent 继续负责对话理解、工具调用和答案生成。
逻辑边界已经清楚,物理拆包会排在路由映射和客户端迁移之后。当前 rag.Service、RAGIndexer、Embedding Client 和 pgvector 访问仍然共享大量实现,太早动它们会把 API 迁移、Worker 迁移和检索行为变化叠在一起。
安全顺序比目录看起来整齐更重要。
Workflow 要拥有完整的事务
当前 Orchestrator 同时承担内容写入、权限级联、访问模拟和成员离职回收。后面会按照完整用户动作拆成多个 Workflow,每个 Workflow 独立注入 UnitOfWork。
这里有一条很重要的约束:
Workflow 之间不能互相调用。
假设一次 Managed Upload 同时需要创建文档、建立 VFS 节点并继承权限,这个动作应该由 WorkspaceWorkflow 持有完整事务。它可以调用权限领域提供的端口,但不会绕去调用另一个 ResourcePolicyWorkflow。
一个用户动作只有一个事务所有者。否则 Workflow 看起来拆开了,事务边界却开始变得隐形,后面排障会更麻烦。
Chat 的沉浸感会保留
前端也有类似的问题。
Workspace 是多面板的工具型界面,适合管理资料、查看目录、编辑内容。Chat 是全屏沉浸式体验,适合连续对话和阅读证据。这两种交互解决的是不同场景。
维护上的收拢会集中在 Auth、API Client、SSE、Chat Runtime、Evidence、Markdown 和 VFS Store。两个 Shell 继续保留各自的 Layout。
未来可以选择两个独立应用,也可以变成一个构建产物里的两套路由。无论采用哪种方式,沉浸式 Chat 都会留下来。
文档和配置也是系统的一部分
这次重构还有一条不太显眼的线:清理仓库里的历史噪声。
282 份 Markdown、279 处 Phase 或历史注释、两份互相冲突的配置模板,这些内容会影响开发者,也会影响参与开发的 AI。
当旧文档还在描述已经不存在的接口,配置模板里出现程序根本不会读取的字段,系统的记忆就开始和源码分叉。
后面的治理会建立一个明确的文档入口。当前架构、API、运维说明、正在进行的工作和历史归档会被分开。配置也只保留一份人工维护的模板,并增加未知键校验。
历史不会消失,它会回到该待的位置。当前事实也会拥有一个真正可信的入口。
写在这次重构之前
到现在为止,这次重构还没有修改一行业务代码。
以前的我大概已经开始写限流中间件,顺手再拆几个 Service。快速完成一套复杂方案,会带来很强的成就感,也很容易被我当成能力的证明。
现在更愿意先停下来。
系统到底要长成什么样子,哪些能力应该收拢,哪些边界需要保留,哪一步可以做到零行为变化,以及如何证明功能一个都没有丢。这些问题没有代码那么显眼,却决定了后面写下的代码会成为资产,还是下一轮维护压力。
这几个月对我的影响,大概也在这里。
我不再急着从别人的评价里确认自己,也不再需要用一套足够复杂的系统证明什么。工作回到了工作本身:看清问题,尊重事实,做出取舍,然后承担结果。
不和别人比较以后,很多东西反而简单了。可以承认过去的设计已经开始变重,也可以承认最初提出的方案并不完整。讨论里出现分歧时,也不需要急着证明谁对谁错,只需要继续沿着代码事实往下推,直到大家找到更好的答案。
这次 KH 的架构,就是这样一点点收敛出来的。
它从限流开始,经过一次关于系统是否臃肿的追问,一路落到了平台入口、共享检索、Workflow 事务和前端双 Shell 的整体收拢上。
代码还没有开始重构,但重构已经发生了。
人有时候也是这样。
真正的变化未必马上产生一个 commit。面对同一个问题时,反应已经和过去不同了。