2025-12-14 HTTP 架构 vs NativeCore 抽象取舍记录
背景
- 当前已确定短中期内,UI 与 Go 核心通过 HTTP 网络通信:
- Go 核心作为本地/局域网服务进程运行。
- Flutter UI 通过 HTTP 调用核心能力。
- 代码中存在两套并行的“核心访问层”设计:
- 一套是实际在用的
ApiClient。- 定义:
client/lib/core/api/api_client.dart:9 - 在
main.dart中实例化,在AppState中注入使用:client/lib/main.dart:33-40client/lib/core/state/app_state.dart:86
- 定义:
- 另一套是抽象的
ICoreService/HttpCoreService/NativeCoreService。- 接口:
client/lib/core/services/core_service.dart:4-53 - HTTP 实现:
client/lib/core/services/http_core_service.dart - Native 预置实现:
client/lib/core/services/native_core_service.dart
- 接口:
- 一套是实际在用的
本次决定的目标:在“不改现有代码”的前提下,把关于“是否还需要维护这套抽象”的思考沉淀成记录,方便未来回顾和重构时参考。
现状梳理
1. 实际使用路径
- 真实调用链:
- UI →
AppState→ApiClient→ Go HTTP 服务。
- UI →
ApiClient已实现并在使用的功能包括:- 认证
initPassword/unlockVault
- 文件操作
uploadFile/downloadFile/deleteFile/updateFileContent
- 目录与元数据操作
createFolder/renameFile/moveFiles/copyFilesgetMetadata
- 缩略图
listThumbnails/getThumbnail/uploadThumbnail/deleteThumbnail
- 流式播放
getStreamUrl/getStreamHeaders
- 认证
这一套 API 与实际业务和 UI 行为完全对齐,是当前“唯一事实来源”。
2. 抽象服务层的状态
ICoreService仅定义了一部分核心操作:- 认证:
initPassword/unlockVault - 元数据:
getMetadata/syncMetadata - 文件:
uploadFile/downloadFile/deleteFile/updateFileContent/listFiles
- 认证:
- 当前问题:
- 没有被 UI / AppState 使用,属于游离抽象。
- 与真实能力不匹配:
- 缺少目录与复制移动接口(
createFolder/renameFile/moveFiles/copyFiles)。 - 缺少缩略图相关接口。
- 流式播放相关只定义了
getStreamUrl/getStreamHeaders,但也未真正被上层依赖。
- 缺少目录与复制移动接口(
NativeCoreService:- 目前全部方法均抛出
UnimplementedError。 - 文件内注释详细描述了 gomobile / cgo 方案,这是一个“未来可能的路线草图”,而非已经在排期中的工作。
- 目前全部方法均抛出
总结:抽象层是一个“设计草案 + 未完成功能”的混合体,目前没有任何实际调用。
不继续推进抽象层的理由
在已经基本确定“短中期不会改成原生接口”的前提下,目前如果强行维护这套抽象,会带来以下成本:
-
接口与事实脱节的维护成本
- 要让
ICoreService真正有用,需要:- 补全所有已经存在于
ApiClient的能力。 - 为每一个新增的后端接口同步更新接口定义与实现。
- 补全所有已经存在于
- 一旦未来忘记同步某个方法,就会出现“接口说有/业务没用”或者“业务有/接口没更新”的不一致,增加心智负担。
- 要让
-
层次复杂度与项目规模不匹配
- 当前调用栈已经足够清晰:
AppState直接依赖ApiClient。 - 再引入
ICoreService/HttpCoreService,会变成:- UI →
AppState→ICoreService→HttpCoreService→Dio→ Go。
- UI →
- 在项目规模尚不大的阶段,这样的间接层更多是负担而不是帮助。
- 当前调用栈已经足够清晰:
-
造成 roadmap 误读的风险
NativeCoreService注释写得很完整,容易让未来的自己误以为:- “Native 路线已经确定要做,只是暂时没空实现。”
- 实际当前判断是:
- UI 与 Go 核心继续通过 HTTP 通信即可满足需求;
- 并没有明确排期要做 FFI/gomobile 方案。
基于以上原因,当前阶段不再在这套抽象上投入额外精力更符合现实需求。
为什么暂时“只记笔记,不动代码”
虽然从工程整洁角度看,完全可以考虑删除整套 core_service.dart / http_core_service.dart / native_core_service.dart,但本次选择暂时不删代码,只立一个决策记录,原因包括:
-
给未来的自己一个观察窗口
- 可以在接下来的开发中观察:
- 是否真的完全不会引用到这套抽象;
- 是否会出现“需要抽象层”的新需求。
- 在没有时间压力的前提下,多观察一阵可以减少“删多了又重写”的往返。
- 可以在接下来的开发中观察:
-
把取舍过程显式化
- 这份笔记记录了:
- 当前真实使用路径(ApiClient 为唯一事实来源)。
- 抽象层与真实能力错位的具体点。
- 决定暂不推进、也暂不删除的理由。
- 未来无论是要彻底删除,还是要复活抽象层,都可以先回到这份记录上重新审视当时的判断。
- 这份笔记记录了:
-
与“清理废弃逻辑”的整体节奏对齐
- 项目里已经在进行一轮代码清理(移除前端兜底、废弃函数、未使用逻辑)。
- 对于抽象层这种“尚有潜在价值但当前不用”的部分,更谨慎的做法是先通过笔记冻结决策,再择机统一处理。
未来可能的两个方向
方向 A:彻底删除抽象层,收敛到 ApiClient
触发条件:
- 项目继续沿着 HTTP 架构发展,没有任何 native 诉求。
- 实际编码中完全没有新地方需要“多实现抽象”。
重构策略(届时可以参考):
- 删除以下文件:
client/lib/core/services/core_service.dartclient/lib/core/services/http_core_service.dartclient/lib/core/services/native_core_service.dart
- 如果文档里仍然需要保留 Native 设想,可以把相关注释迁移到 devdaily,而不是放在代码里。
预期收益:
- 减少一层概念和文件,整体结构更直观:
- UI →
AppState→ApiClient→ Go。
- UI →
- 降低未来阅读和维护门槛。
方向 B:重新设计一套真正有用的领域抽象
触发条件:
- 出现以下任意情况:
- 除了 HTTP 实现之外,确实需要第二种实现(例如:离线模式、本地缓存层、Native FFI)。
- 核心逻辑明显复杂到需要一个稳定的、与传输协议解耦的“领域接口”。
重构策略(届时可以参考):
- 不直接复活现在的
ICoreService,而是:- 以当前
ApiClient和AppState中实际用到的能力为准,重新定义接口。 - 将接口按“领域边界”设计,而不是简单映射 HTTP 路径。
- 为 HTTP 实现写一个适配层,内部仍然复用已有
ApiClient。
- 以当前
- 根据实际需求决定是否还需要
NativeCoreService,避免为了“可能有一天”而提前设计。
预期收益:
- 如果真的走到多实现阶段,可以给 UI 留一个更稳定的依赖面。
- 抽象层的设计会基于“真实演进”而不是“提前臆测”。
当前共识(2025-12-14)
- 架构共识:
- 短中期内,UI 与 Go 核心继续通过 HTTP 通信。
- 不主动推进 Native FFI / gomobile 方案。
- 代码层策略:
ApiClient作为唯一事实来源和 HTTP 边界层,继续维护和演进。- 现有
ICoreService/HttpCoreService/NativeCoreService暂时不改动,也不再扩展。 - 等后续有更明确的需求或清理窗口时,再在这份笔记的基础上决定:
- 是彻底删除抽象层,还是重启、重设计。
这份记录的作用是:给“当下的决定”一个可追溯的解释,避免未来回头看到这些文件时,需要重新花时间推断当时的意图。