Skip to content

ADR-004: 服务层接口抽象设计

Status

Accepted

Date

2026-03-25

Context

前端需要调用后端服务(AI 生成、画布管理、模型查询、文件上传等)。需要决定如何组织服务调用代码,使其:

  1. 可测试 — 单元测试能隔离网络层
  2. 职责清晰 — 业务组件不直接操作 HTTP
  3. 可替换 — 未来如需切换 API 提供者,改动范围最小

Options Considered

方案 A:组件内直接调用 $fetch

  • 优点:简单直接
  • 缺点:HTTP 细节散布在组件中;无法统一错误处理;测试需要 mock 全局 $fetch

方案 B:两层抽象 — ApiClient + Service 接口

  • 优点:ApiClient 统一处理 headers、错误转换;各 Service 定义业务接口,实现可替换;测试只需 mock ApiClient
  • 缺点:多一层间接

方案 C:Nuxt useFetch / useAsyncData

  • 优点:Nuxt 原生、支持 SSR
  • 缺点:与 Nuxt 生命周期耦合;节点生成是用户触发的命令式操作,不适合声明式数据获取

Decision

选择 方案 B:两层抽象

组件/Store → Service (业务接口) → ApiClient (HTTP 封装) → 后端 API
  • ApiClientservices/http/client.ts):封装 $fetch,统一 headers(App-IdPlatform)、响应解包(ApiResponse<T>T)、业务错误转换(ApiError
  • 各 Service 模块(services/generate/services/canvas/services/models/services/workspace/services/storage/):定义业务方法,接收 ApiClient
  • useApiClientservices/http/index.ts):Nuxt composable,从运行时配置读取 apiBase 创建单例

Consequences

正面影响:

  • 业务组件只依赖 Service 接口,不感知 HTTP 细节
  • 单元测试 mock ApiClient 即可,无需 mock 全局 $fetch
  • 新增服务只需在对应 Service 模块添加方法

负面影响 / 已知风险:

  • ApiClientApp-IdPlatform 硬编码在 client.ts 中,如需动态配置需要修改工厂函数

替代此决策的条件

WebSocket 通信已独立为 services/ws/ 模块,不经过 ApiClient。若引入 GraphQL,需重新评估 HTTP 层假设。