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