干净室插件架构实践:把遥测工作流拆成可替换插件

作者:

在

结论:要做干净室、插件化的遥测工具重构,落点是把「宿主」压缩到最小、把能力全部下沉到可独立加载与卸载的插件。本文的取舍可以概括为三条:宿主只负责插件生命周期、工作区、命令路由与能力发现;所有数据模型与契约位于中立的 Abstractions 层,不依赖任何 UI 框架;单个插件加载失败只进入失败清单,不阻止无依赖插件运行。清单的 schemaVersion 当前为 1,插件 ID 使用反向域名风格,通道名使用稳定点分命名(如 speed.kmh)。

七条设计原则

架构文档给出的七条原则,决定了后续所有工程取舍:

原则 含义
宿主最小化 宿主只负责插件生命周期、工作区、命令路由和能力发现
数据中立 会话、采样、圈、分段、视频引用与弯角定义位于 Abstractions,不依赖 UI 或第三方库
多实现并存 同一契约可注册多个实现,例如 CSV、GeoJSON 与报告都实现同一个导出契约
依赖显式 依赖写在 plugin.json,加载器做拓扑排序并检测循环
故障隔离 单个插件加载失败进入失败清单,不阻止无依赖插件运行
UI 是适配器 CLI、桌面与 Web 都是宿主外壳,不能持有核心业务算法
安全默认值 更新源只接受 HTTPS;插件加载目录必须由宿主明确指定

第四条与第五条合起来,是插件架构能否长期维护的关键:依赖必须显式声明,才能在加载阶段做拓扑排序、定位失败插件并隔离它。

插件生命周期与加载隔离

加载流程是固定的一段顺序,可按下面步骤核对:

  1. 发现 plugin.json;
  2. 验证 schemaVersion;
  3. 定位 entryAssembly;
  4. 依赖拓扑排序;
  5. 创建独立 AssemblyLoadContext;
  6. 实例化插件接口;
  7. 注册服务与命令;
  8. 启动(StartAsync);
  9. 正常运行;
  10. 停止(StopAsync);
  11. 卸载 AssemblyLoadContext。

这里有一条容易踩坑的规则:共享的 Abstractions 程序集始终由默认加载上下文提供,插件加载上下文不会加载自己的契约副本。这么做是为了避免 CLR 类型身份不一致——如果每个插件各自加载一份契约,同一接口在不同上下文里会被当成不同类型,注册与调用都会失败。

能力契约与功能拆分映射

宿主通过一组能力契约暴露功能,插件只实现自己关心的契约:

契约 用途
数据导入契约 文件到遥测会话
会话处理契约 导入后的派生通道与标准化处理
数据导出契约 会话到外部文件
弯角库服务 赛道弯角库读取
圈速服务 门线、圈速、分段
视频引用服务 视频关联和路径解析
图表序列服务 图表序列和抽样
地图路线服务 地图轨迹和简化
洞察分析器 弯角分析
配置存储 配置持久化
更新源服务 安全更新元数据
赛道库服务 赛道、门线和位置查询
文件传输服务 会话文件分组和复制
插件命令 可脚本化的用户命令

原软件里集中的「服务」被拆成独立边界。功能域的映射关系如下:

原功能域 对应插件
文件读写与数据服务 数据导入插件
弯角数据与赛道路径 弯角插件
起终点数据库 赛道数据库插件
开放赛道几何 开放赛道数据插件
数学通道 数学插件
圈与分段 圈速插件
视频服务 视频插件(播放后端作为后续适配器)
图表、地图、导出、报告、洞察 各自独立的图表 / 地图 / 导出 / 报告 / 洞察插件
设置、自动更新、文件传输 设置 / 更新 / 文件传输插件

桌面层延续同样原则:外壳只提供停靠区、菜单、状态栏与工作区选择;图表插件贡献图表面板、不直接引用导入插件;地图插件只读标准位置通道;视频插件可接入可替换的播放后端;PDF、打印、HTML 报告分别实现同一个报告渲染契约;在线地图、遥测上传与 AI 建议必须是显式安装、可禁用的网络插件。

干净室边界:能用什么、不能带进什么

干净室开发的核心是划一条清晰的输入边界,只允许来自可观察行为与公开资料的证据:

可以使用 不进入新工程
用户可观察的输入、输出和交互行为 原程序的反编译源码或轻微改名的等价复制
公开文档、公开文件格式和独立测试数据 保护区域中恢复出的专有方法体
为互操作性而确认的字段、单位和二进制布局 厂商图标、图片、文案、赛道数据库等受保护资源
自行设计的数据模型、算法、插件接口和用户界面 厂商 DLL、许可组件、私有密钥或未授权在线服务
— 绕过许可、签名验证或访问控制的逻辑

工程上还有几条落地措施:源码、插件与测试目录不引用反编译目录;测试用的采集文件为人工合成数据;文件格式解析器只实现互操作,测试时从用户本机安装读取、不把数据库复制进仓库;可分发的赛道与弯角数据包只从明确许可的开放数据源生成,并在每个快照中保留来源、查询、署名和文件哈希;更新插件拒绝 HTTP 源且不自动下载或安装包。

新增一个插件的五步

  1. 新建一个引用 Abstractions 的目标框架类库;
  2. 实现插件接口;
  3. 通过注册器注册服务或命令;
  4. 在项目根目录放置 plugin.json,声明入口程序集、入口类型、依赖和能力;
  5. 把项目加入解决方案并构建,宿主会从输出目录自动定位程序集。

兼容性规则同时要求:插件不得引用反编译目录或厂商二进制;破坏性契约变化要发布新的 Abstractions 主版本。把这两条与前面的「依赖显式」放在一起看,就能理解为什么插件清单必须声明入口程序集、入口类型、依赖与能力——加载器据此排序、隔离并在失败时给出清单,而不是静默跳过。

常见问题

单个插件加载失败会不会拖垮整个工具?

不会。架构原则要求故障隔离:单个插件加载失败进入失败清单,不阻止无依赖的插件运行。前提是依赖显式写在清单里,加载器才能做拓扑排序并隔离失败项。

为什么每个插件各自加载一份契约会出问题?

会造成 CLR 类型身份不一致:同一接口在不同加载上下文里被当成不同类型,注册与调用都会失败。因此共享的契约程序集始终由默认加载上下文提供,插件加载上下文不加载自己的契约副本。

哪些数据可以来自原软件?

只有用户可观察的输入、输出与交互行为,以及公开文档、公开文件格式和独立测试数据。反编译源码、保护区域恢复的专有方法体、厂商图标文案、厂商二进制与私有密钥都不进入新工程。

网络、在线地图和 AI 能力怎么处理?

它们必须是显式安装、可禁用的独立网络插件。更新源只接受 HTTPS,且不自动下载或安装包;这类能力应保持默认关闭、可审计。

延伸阅读

参考资料

  • 项目 03 —— nixiang-tracklab/PLUGIN_ARCHITECTURE.md(取用七条设计原则、插件生命周期、能力契约、功能拆分映射与兼容性规则)。
  • 项目 03 —— nixiang-tracklab/CLEAN_ROOM.md(取用干净室的可使用 / 不进入边界与工程措施)。
  • 项目 03 —— nixiang-tracklab/README.md(取用当前插件状态、构建运行方式与新增插件五步)。

说明:本文为对整洁室重构文档与固定源码快照的静态结构整理,未运行完整测试套件、未连接任何厂商数据库或车辆、未验证各插件在实际数据上的输出。文中所有原则、契约与步骤均出自源文档,标注为未验证的结论保留其原有边界。

更新记录

首次发布 2026-10-11 最近核对:2026-10-11

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注