ADR-001: 提前设计资源框架
Status: 已采纳 (Accepted) Date: 2026-05-20 Deciders: Yuxing Wu Related: .specify/memory/constitution.md (II Spec-as-Truth, Architectural Constraints), ADR-002, spec 001-mcp-gateway
背景
Coffer 长期来看需要管理多种资源 (resource) 类型 (kind)。首份规约 (001-mcp-gateway) 仅落地 mcp_server 这一种具体 kind。问题是:要在此时 就设计资源抽象 —— 即便只有一种 kind 是具体的 —— 还是等到第二种 kind 出现时再做。
章程 (constitution) 明确写道:
横切模块只在第二个功能需要它时才抽取。
这一默认规则倾向于推迟抽象。我们必须先判断:资源框架究竟属于「横切模块」 (受该规则约束),还是「核心领域概念」(不受该规则约束)。
反向压力在于:用户已经明确地、有把握地列出了未来几种 kind。如果现在不做, 等第二种 kind 出现时,再去重构本次规约中的 DB schema、审计 (audit) 管道、 留存 (retention) 框架与界面路由,成本绝非小事。归根结底:「现在设计一次」 是否比「先设计、再重构」更便宜。
决定
在规约 001-mcp-gateway 中将资源框架设计为核心领域:
Resource是顶层领域实体,具备与 kind 无关的身份标识 (<kind>:<name>)、生命周期(注册 / 更新 / 启用 / 停用 / 删除)、审计 以及按 kind 校验的配置。- 该框架统一身份 + 生命周期 + 审计 + 元数据。它不统一行为 —— 调用语义因 kind 而异。
- MCP gateway 是接入该框架的第一种具体 kind,而非一次性功能。
章程中关于横切模块的条款不适用,因为资源属于项目核心领域模型, 而非横切基础设施(如日志、遥测或鉴权中间件)。
后果
正面
- 未来新增 kind 可机械化接入:编写
<layer>/<new_kind>/,在组合根 (composition root) 注册一个KindModule即可 —— 无需 schema 迁移,无需重连审计, 无需重整留存管道。 - 审计、留存、资源列表 UI 从第一天起就与 kind 无关。
- 避免了首次重构成本(在第二种 kind 到来时从 MCP 专属代码里抽取资源)。
- 从一开始就明确了框架与 kind 之间的边界,降低了 MCP 专属假设渗入框架代码的风险。
负面
- 本规约只有一种具体 kind 来支撑这一抽象的开销。
- 必须严守框架的行为边界:试图跨 kind 统一调用语义(一个「全能的
invoke()方法」)是缺陷而非特性。详见下文备选方案。 - ADR-002 (代码布局) 被迫成为同一规约内的决策,因为框架横跨每一层。
后续
- 当首次出现跨 kind的关注点(例如 MCP 和 skill 都需要子进程监管)落入 kind 代码内部时,按章程的「第二个功能时抽取」规则正常处理 —— 资源框架并不是 对一切都提前抽象的免死金牌。
备选方案
第二个功能时抽取(章程默认)。被否决:通用资源框架无法在不一并重构审计表、 界面路由与留存框架的前提下,从 MCP 专属代码中干净地抽取出来。「第二个功能」 的成本是一次相当规模的重构,而非小幅抽离。
完整插件架构(第三方可加载的 kind)。被否决:可用的插件契约只能在面对 ≥ 2 个具体实现时才能设计清楚;只对 MCP 一种实现做设计,要么过度贴合 MCP, 要么过于宽松而无法约束任何东西。Coffer 是单用户、本地优先的 OSS 工具 —— 在当前规模上没有真实的第三方插件生态需要服务。
按 kind 各自为战,不抽象共享层。被否决:用户已经有较高把握地列出 ≥ 4 种 未来 kind,它们都共享身份、生命周期、审计和界面层的 CRUD。重复实现四次的 代码量与漂移风险都比维护一个框架更高。