Appearance
ADR-004: 服务层接口抽象设计
Status
Accepted
Date
2026-03-25
Context
前端需要调用后端服务(AI 生成、画布管理、模型查询、文件上传等)。需要决定如何组织服务调用代码,使其:
- 可测试 — 单元测试能隔离网络层
- 职责清晰 — 业务组件不直接操作 HTTP
- 可替换 — 未来如需切换 API 提供者,改动范围最小
Options Considered
方案 A:组件内直接调用 $fetch
- 优点:简单直接
- 缺点:HTTP 细节散布在组件中;无法统一错误处理;测试需要 mock 全局
$fetch
方案 B:两层抽象 — ApiClient + Service 接口
- 优点:
ApiClient统一处理 headers、错误转换;各 Service 定义业务接口,实现可替换;测试只需 mockApiClient - 缺点:多一层间接
方案 C:Nuxt useFetch / useAsyncData
- 优点:Nuxt 原生、支持 SSR
- 缺点:与 Nuxt 生命周期耦合;节点生成是用户触发的命令式操作,不适合声明式数据获取
Decision
选择 方案 B:两层抽象。
组件/Store → Service (业务接口) → ApiClient (HTTP 封装) → 后端 APIApiClient(services/http/client.ts):封装$fetch,统一 headers(App-Id、Platform)、响应解包(ApiResponse<T>→T)、业务错误转换(ApiError)- 各 Service 模块(
services/generate/、services/canvas/、services/models/、services/workspace/、services/storage/):定义业务方法,接收ApiClient useApiClient(services/http/index.ts):Nuxt composable,从运行时配置读取apiBase创建单例
Consequences
正面影响:
- 业务组件只依赖 Service 接口,不感知 HTTP 细节
- 单元测试 mock
ApiClient即可,无需 mock 全局$fetch - 新增服务只需在对应 Service 模块添加方法
负面影响 / 已知风险:
ApiClient的App-Id和Platform硬编码在client.ts中,如需动态配置需要修改工厂函数
替代此决策的条件
WebSocket 通信已独立为 services/ws/ 模块,不经过 ApiClient。若引入 GraphQL,需重新评估 HTTP 层假设。