2026-05-18 13:29:57 +08:00
|
|
|
|
# Backend Project Architecture
|
|
|
|
|
|
|
|
|
|
|
|
## 1. Purpose
|
|
|
|
|
|
|
|
|
|
|
|
本文档定义当前 backend 的目标态架构,用于在保持单服务部署的前提下,将系统整理为职责清晰、边界稳定、可替换实现的模块化结构。本文档的重点不是描述理想化分层,而是基于当前真实代码形态,明确后续重构时必须遵守的模块职责、依赖方向、内部稳定接口和替换边界。
|
|
|
|
|
|
|
|
|
|
|
|
本文档与 `docs/rfc/backend-api-parsing-embedding-migration-requirements.md` 的关系如下:
|
|
|
|
|
|
|
|
|
|
|
|
- RFC 负责冻结本轮迁移需求、范围、风险和约束。
|
|
|
|
|
|
- 本文档负责冻结目标模块边界、依赖规则和实现组织方式。
|
|
|
|
|
|
- 后续任何代码重构、能力替换或底座升级,都应同时满足 RFC 与本文档。
|
|
|
|
|
|
|
|
|
|
|
|
## 2. Current-State Problems
|
|
|
|
|
|
|
|
|
|
|
|
基于当前代码,后端已经具备以下能力:
|
|
|
|
|
|
|
|
|
|
|
|
- 文档上传、下载、列表
|
|
|
|
|
|
- 文档解析与切片
|
|
|
|
|
|
- 向量化与 Milvus 入库
|
|
|
|
|
|
- 检索
|
|
|
|
|
|
- 基于 RAG 的 Agent 问答 workflow
|
|
|
|
|
|
|
|
|
|
|
|
但这些能力当前主要是“可运行”,还不是“结构清晰、便于替换、便于演进”的状态。核心问题如下。
|
|
|
|
|
|
|
|
|
|
|
|
### 2.1 `DocumentProcessor` 责任过载
|
|
|
|
|
|
|
2026-05-18 16:32:42 +08:00
|
|
|
|
现状判断:
|
|
|
|
|
|
|
|
|
|
|
|
- `backend/app/services/document_processor.py` 已经不再是当前主要编排点
|
|
|
|
|
|
- 上传、查询和问答的核心组织逻辑已经明显下沉到 `backend/app/application/*` 以及 `backend/app/shared/bootstrap.py`
|
|
|
|
|
|
- `DocumentProcessor` 当前更接近一个兼容旧调用路径的 legacy façade
|
|
|
|
|
|
|
|
|
|
|
|
其历史上曾集中承载过以下职责:
|
2026-05-18 13:29:57 +08:00
|
|
|
|
|
|
|
|
|
|
- 文档解析
|
|
|
|
|
|
- 摘要生成
|
|
|
|
|
|
- 分块
|
|
|
|
|
|
- 向量化
|
|
|
|
|
|
- Milvus 入库
|
|
|
|
|
|
- 检索入口
|
|
|
|
|
|
|
2026-05-18 16:32:42 +08:00
|
|
|
|
当前真正还需要收口的问题不是“继续拆这个大类”,而是把文档元数据、解析、分块、向量化和索引写入的稳定边界进一步收束到 application service 与端口接口中,避免后续重构继续围绕旧实现形态做判断。
|
2026-05-18 13:29:57 +08:00
|
|
|
|
|
|
|
|
|
|
### 2.2 检索逻辑缺少稳定边界
|
|
|
|
|
|
|
2026-05-18 16:32:42 +08:00
|
|
|
|
现状判断:
|
|
|
|
|
|
|
|
|
|
|
|
- `backend/app/services/rag/retriever.py` 已经明显退化为兼容层
|
|
|
|
|
|
- 真正承担检索编排的是 `KnowledgeRetrievalService` 和 `shared/bootstrap` 中的 wiring
|
|
|
|
|
|
- `Retriever` 应被视为业务侧端口,而不是当前主编排单元
|
|
|
|
|
|
|
|
|
|
|
|
其历史上曾集中承载过以下职责:
|
2026-05-18 13:29:57 +08:00
|
|
|
|
|
|
|
|
|
|
- embedder 初始化
|
|
|
|
|
|
- Milvus 连接与 collection lifecycle
|
|
|
|
|
|
- 检索执行
|
|
|
|
|
|
- 结果映射
|
|
|
|
|
|
|
2026-05-18 16:32:42 +08:00
|
|
|
|
所以这里的重点不是把旧 retriever 当成未来架构核心,而是确认检索能力的稳定边界已经迁移到 application 层,并继续清理仍然残留的 provider-specific 细节。
|
2026-05-18 13:29:57 +08:00
|
|
|
|
|
|
|
|
|
|
### 2.3 `QAAgent` 责任过载
|
|
|
|
|
|
|
2026-05-18 16:32:42 +08:00
|
|
|
|
现状判断:
|
|
|
|
|
|
|
|
|
|
|
|
- `backend/app/services/agent/qa_agent.py` 已经不是当前主要问答编排点
|
|
|
|
|
|
- 当前实际的问答编排已经主要落到 `AgentConversationService`
|
|
|
|
|
|
- `QAAgent` 当前更接近向后兼容的 legacy façade
|
|
|
|
|
|
|
|
|
|
|
|
其历史上曾集中承载过以下职责:
|
2026-05-18 13:29:57 +08:00
|
|
|
|
|
|
|
|
|
|
- 检索调用
|
|
|
|
|
|
- 上下文构建
|
|
|
|
|
|
- Prompt 选择
|
|
|
|
|
|
- LLM 调用
|
|
|
|
|
|
- SSE 流式问答流程
|
|
|
|
|
|
- 会话 workflow 编排
|
|
|
|
|
|
|
2026-05-18 16:32:42 +08:00
|
|
|
|
剩余风险不在于“它还很大”,而在于仍有少量 API 入口和 session 读写没有完全收口到 application service,导致目标边界和现状之间还存在断点。
|
2026-05-18 13:29:57 +08:00
|
|
|
|
|
|
|
|
|
|
### 2.4 API 层直接编排具体服务
|
|
|
|
|
|
|
|
|
|
|
|
当前 API 路由主要在:
|
|
|
|
|
|
|
|
|
|
|
|
- `backend/app/api/routes/documents.py`
|
|
|
|
|
|
- `backend/app/api/routes/knowledge.py`
|
|
|
|
|
|
- `backend/app/api/routes/agent.py`
|
|
|
|
|
|
|
|
|
|
|
|
这些路由直接实例化具体服务类,例如 `DocumentProcessor`、`QAAgent`、`MinIOClient`。这意味着:
|
|
|
|
|
|
|
|
|
|
|
|
- API 层不仅处理 transport concerns,也在做业务编排
|
|
|
|
|
|
- 路由层知道过多内部实现细节
|
|
|
|
|
|
- 后续如果内部模块调整,路由层也要跟着改
|
|
|
|
|
|
|
2026-05-18 16:32:42 +08:00
|
|
|
|
现状里这条判断需要修正:大部分主流程已经通过 `backend/app/shared/bootstrap.py` 统一装配,并由 application service 承担编排;真正还没有完全收口的是 `backend/app/api/routes/agent.py` 中的 session/history/feedback 这类接口,它们仍直接访问 `ConversationStore`。
|
|
|
|
|
|
|
|
|
|
|
|
因此,当前问题不是“路由层仍然是主编排中心”,而是“少数会话管理接口还没有通过 application service 统一封装”,这会持续打破 API 只做 transport concerns 的边界。
|
|
|
|
|
|
|
2026-05-18 13:29:57 +08:00
|
|
|
|
### 2.5 文档元数据与对象存储组织方式耦合
|
|
|
|
|
|
|
|
|
|
|
|
当前文档列表与下载逻辑高度依赖 MinIO 对象命名方式和对象遍历结果。对象存储目前承担了部分“业务真相”的角色,但对象存储只适合作为文件二进制载体,不适合作为完整文档元数据和状态管理的唯一来源。
|
|
|
|
|
|
|
|
|
|
|
|
### 2.6 `knowledge` 与 `agent` 共享检索底座的边界不清晰
|
|
|
|
|
|
|
|
|
|
|
|
当前 `/knowledge/*` 与 `/agent/*` 都依赖检索能力,但共享方式不够清晰:
|
|
|
|
|
|
|
|
|
|
|
|
- `knowledge` 通过 `DocumentProcessor.search()` 访问检索
|
|
|
|
|
|
- `agent` 通过 `Retriever` 访问检索
|
|
|
|
|
|
|
2026-05-18 16:32:42 +08:00
|
|
|
|
这条问题描述需要更新。现在检索主链路已经被 `KnowledgeRetrievalService` 统一收束,`knowledge` 与 `agent` 的共享底座比过去清晰得多。
|
|
|
|
|
|
|
|
|
|
|
|
当前仍未完全收口的是 Agent 的 session 相关能力:公开 API 里还存在 `/agent/session/{id}`、`/agent/session/{id}/history`、`/agent/sessions`、`/agent/feedback` 等接口直接读写 `ConversationStore`,这意味着“问答编排统一了,但会话管理还没有统一入口”。
|
2026-05-18 13:29:57 +08:00
|
|
|
|
|
|
|
|
|
|
## 3. Architecture Goals
|
|
|
|
|
|
|
|
|
|
|
|
本项目后端的目标态架构必须满足以下目标。
|
|
|
|
|
|
|
|
|
|
|
|
### 3.1 单服务部署
|
|
|
|
|
|
|
|
|
|
|
|
系统继续保持单服务部署,不拆分为多个微服务。架构治理发生在单服务内部,通过清晰模块边界实现高内聚低耦合,而不是通过进程级拆分回避设计问题。
|
|
|
|
|
|
|
|
|
|
|
|
### 3.2 高内聚、低耦合优先级最高
|
|
|
|
|
|
|
|
|
|
|
|
后续模块设计以“一个模块只承载一类稳定职责”为原则。跨能力流程统一在编排层组织,不允许继续把 parser、embedding、storage、retrieval、LLM workflow 堆进同一个服务类。
|
|
|
|
|
|
|
|
|
|
|
|
### 3.3 外部 API 尽量保持兼容
|
|
|
|
|
|
|
|
|
|
|
|
现有前端与外部调用方依赖的主接口保持不变优先,包括但不限于:
|
|
|
|
|
|
|
|
|
|
|
|
- `/api/v1/documents/*`
|
|
|
|
|
|
- `/api/v1/knowledge/*`
|
|
|
|
|
|
- `/api/v1/agent/*`
|
|
|
|
|
|
|
|
|
|
|
|
内部可以重组,但外部接口不应因为内部重构而被迫大改。
|
|
|
|
|
|
|
|
|
|
|
|
### 3.4 关键能力必须可替换
|
|
|
|
|
|
|
|
|
|
|
|
以下能力必须通过稳定端口隔离实现细节:
|
|
|
|
|
|
|
|
|
|
|
|
- 文档解析
|
|
|
|
|
|
- 分块构建
|
|
|
|
|
|
- 向量化
|
|
|
|
|
|
- 向量索引
|
|
|
|
|
|
- 检索
|
|
|
|
|
|
- LLM 回答生成
|
|
|
|
|
|
- 会话存储
|
|
|
|
|
|
- 原始文件存储
|
|
|
|
|
|
|
|
|
|
|
|
后续替换方案时,只允许替换实现,不允许穿透影响其他模块。
|
|
|
|
|
|
|
|
|
|
|
|
### 3.5 `knowledge` 与 `agent` 共用同一检索底座
|
|
|
|
|
|
|
|
|
|
|
|
检索必须被视为独立的业务能力,由统一的 retrieval application service 对外暴露。`knowledge` 与 `agent` 必须复用同一个 retrieval 底座,避免两套召回策略、两套元数据模型、两套 adapter。
|
|
|
|
|
|
|
|
|
|
|
|
### 3.6 依赖必须单向流动
|
|
|
|
|
|
|
|
|
|
|
|
系统必须形成稳定的单向依赖关系:
|
|
|
|
|
|
|
|
|
|
|
|
- `api -> application -> domain`
|
2026-05-18 16:32:42 +08:00
|
|
|
|
- `application -> domain ports`
|
|
|
|
|
|
- `composition root -> application + infrastructure bindings`
|
2026-05-18 13:29:57 +08:00
|
|
|
|
- `infrastructure -> external systems`
|
|
|
|
|
|
|
|
|
|
|
|
不允许出现基础设施实现反向驱动业务编排,也不允许 domain 依赖 Web 或第三方 SDK。
|
|
|
|
|
|
|
|
|
|
|
|
## 4. Target Module Layout
|
|
|
|
|
|
|
|
|
|
|
|
目标目录结构如下:
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
backend/app/
|
|
|
|
|
|
api/
|
|
|
|
|
|
application/
|
|
|
|
|
|
documents/
|
|
|
|
|
|
knowledge/
|
|
|
|
|
|
agent/
|
|
|
|
|
|
domain/
|
|
|
|
|
|
documents/
|
|
|
|
|
|
retrieval/
|
|
|
|
|
|
conversation/
|
|
|
|
|
|
infrastructure/
|
|
|
|
|
|
storage/
|
|
|
|
|
|
vectorstore/
|
|
|
|
|
|
parser/
|
|
|
|
|
|
embedding/
|
|
|
|
|
|
llm/
|
|
|
|
|
|
session/
|
|
|
|
|
|
shared/
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
该结构是本项目 backend 的目标态模块布局。后续实现可以渐进迁移,但职责边界不能偏离。
|
|
|
|
|
|
|
|
|
|
|
|
### 4.1 `api`
|
|
|
|
|
|
|
|
|
|
|
|
职责:
|
|
|
|
|
|
|
|
|
|
|
|
- HTTP 路由注册
|
|
|
|
|
|
- 请求参数校验
|
|
|
|
|
|
- 响应模型映射
|
|
|
|
|
|
- 异常转换
|
|
|
|
|
|
- SSE 事件格式输出
|
|
|
|
|
|
|
|
|
|
|
|
非职责:
|
|
|
|
|
|
|
|
|
|
|
|
- 不直接组织完整业务流程
|
|
|
|
|
|
- 不直接访问 MinIO、Milvus、Parser SDK、LLM SDK
|
|
|
|
|
|
- 不直接 new 具体基础设施客户端
|
|
|
|
|
|
|
|
|
|
|
|
### 4.2 `application`
|
|
|
|
|
|
|
|
|
|
|
|
职责:
|
|
|
|
|
|
|
|
|
|
|
|
- 用例编排
|
|
|
|
|
|
- 跨领域能力协作
|
|
|
|
|
|
- 业务流程统一入口
|
|
|
|
|
|
- workflow 级别的状态推进
|
|
|
|
|
|
|
|
|
|
|
|
非职责:
|
|
|
|
|
|
|
|
|
|
|
|
- 不直接依赖第三方 SDK
|
|
|
|
|
|
- 不承担具体存储、向量库、解析器实现细节
|
|
|
|
|
|
|
|
|
|
|
|
### 4.3 `domain`
|
|
|
|
|
|
|
|
|
|
|
|
职责:
|
|
|
|
|
|
|
|
|
|
|
|
- 核心业务对象
|
|
|
|
|
|
- 领域术语
|
|
|
|
|
|
- 稳定端口接口
|
|
|
|
|
|
- 统一元数据模型
|
|
|
|
|
|
- 检索结果模型
|
|
|
|
|
|
- 会话消息模型
|
|
|
|
|
|
|
|
|
|
|
|
非职责:
|
|
|
|
|
|
|
|
|
|
|
|
- 不依赖 FastAPI
|
|
|
|
|
|
- 不依赖 MinIO、Milvus、LLM SDK
|
|
|
|
|
|
- 不依赖路由请求响应模型
|
|
|
|
|
|
|
|
|
|
|
|
### 4.4 `infrastructure`
|
|
|
|
|
|
|
|
|
|
|
|
职责:
|
|
|
|
|
|
|
|
|
|
|
|
- 外部系统适配器实现
|
|
|
|
|
|
- 第三方 SDK 封装
|
|
|
|
|
|
- provider-specific 配置适配
|
|
|
|
|
|
- 数据格式转换
|
|
|
|
|
|
|
|
|
|
|
|
包含但不限于:
|
|
|
|
|
|
|
|
|
|
|
|
- MinIO binary store
|
|
|
|
|
|
- Milvus vector index
|
|
|
|
|
|
- Aliyun / local parser adapter
|
|
|
|
|
|
- OpenAI-compatible embedding adapter
|
|
|
|
|
|
- DeepSeek / Qwen LLM adapter
|
|
|
|
|
|
- in-memory / Redis session store
|
|
|
|
|
|
|
|
|
|
|
|
### 4.5 `shared`
|
|
|
|
|
|
|
|
|
|
|
|
职责:
|
|
|
|
|
|
|
|
|
|
|
|
- 配置
|
|
|
|
|
|
- 日志
|
|
|
|
|
|
- 通用异常
|
|
|
|
|
|
- 通用工具
|
|
|
|
|
|
- 公共基础设施无关组件
|
2026-05-18 16:32:42 +08:00
|
|
|
|
- composition root 与依赖装配
|
|
|
|
|
|
- 运行时共享的 wiring 辅助入口
|
2026-05-18 13:29:57 +08:00
|
|
|
|
|
|
|
|
|
|
非职责:
|
|
|
|
|
|
|
|
|
|
|
|
- 不承载业务编排
|
|
|
|
|
|
- 不变成新的 `services` 大杂烩目录
|
|
|
|
|
|
|
2026-05-18 16:32:42 +08:00
|
|
|
|
说明:
|
|
|
|
|
|
|
|
|
|
|
|
- `shared` 不是业务层,但它是本项目当前明确的装配层。
|
|
|
|
|
|
- `backend/app/shared/bootstrap.py` 是现阶段的 composition root,负责把端口实现、基础设施适配器和 application service 连接起来。
|
|
|
|
|
|
- 后续如果新增 wiring 入口,应继续保持在同一类装配边界内,不要把依赖装配拆回各个路由或 service 构造函数中。
|
|
|
|
|
|
|
2026-05-18 13:29:57 +08:00
|
|
|
|
## 5. Module Responsibilities
|
|
|
|
|
|
|
|
|
|
|
|
### 5.1 `api`
|
|
|
|
|
|
|
|
|
|
|
|
`api` 是 transport 层,只关心请求进来和响应出去的表达方式。它应该把请求转换为 application service 的输入,把 application service 的结果转换为 HTTP 响应。
|
|
|
|
|
|
|
|
|
|
|
|
`api` 不应该知道:
|
|
|
|
|
|
|
|
|
|
|
|
- MinIO bucket 怎么组织
|
|
|
|
|
|
- Milvus collection 怎么建
|
|
|
|
|
|
- parser 是本地还是阿里云
|
|
|
|
|
|
- embedding 是本地模型还是 API
|
|
|
|
|
|
- session 是内存还是 Redis
|
|
|
|
|
|
|
|
|
|
|
|
### 5.2 `application`
|
|
|
|
|
|
|
|
|
|
|
|
`application` 是业务编排层,是系统内唯一允许跨模块组织完整流程的层。它应该定义稳定的用例服务,而不是把流程散落在路由或基础设施实现中。
|
|
|
|
|
|
|
|
|
|
|
|
本项目至少固定以下 4 类 application service:
|
|
|
|
|
|
|
|
|
|
|
|
- `DocumentCommandService`
|
|
|
|
|
|
- `DocumentQueryService`
|
|
|
|
|
|
- `KnowledgeRetrievalService`
|
|
|
|
|
|
- `AgentConversationService`
|
|
|
|
|
|
|
2026-05-18 16:32:42 +08:00
|
|
|
|
说明:
|
|
|
|
|
|
|
|
|
|
|
|
- 这 4 类是当前已经明确成型的核心用例服务,但不是 application 的全部上限。
|
|
|
|
|
|
- 现有 Agent session 管理接口所需的会话查询、历史读取、删除和反馈归档能力,后续应补入 application 层的明确用例服务或子服务,而不是继续让 API 直连 `ConversationStore`。
|
|
|
|
|
|
|
2026-05-18 13:29:57 +08:00
|
|
|
|
### 5.3 `domain`
|
|
|
|
|
|
|
|
|
|
|
|
`domain` 层定义系统内部真正稳定的概念,例如:
|
|
|
|
|
|
|
|
|
|
|
|
- `Document`
|
|
|
|
|
|
- `DocumentStatus`
|
|
|
|
|
|
- `ParsedDocument`
|
|
|
|
|
|
- `Chunk`
|
|
|
|
|
|
- `RetrievalQuery`
|
|
|
|
|
|
- `RetrievedChunk`
|
|
|
|
|
|
- `ConversationSession`
|
|
|
|
|
|
- `ConversationMessage`
|
|
|
|
|
|
- `AnswerSource`
|
|
|
|
|
|
|
|
|
|
|
|
这些对象必须脱离具体技术实现,成为 parser、embedding、vector index、agent workflow 之间的公共契约。
|
|
|
|
|
|
|
|
|
|
|
|
### 5.4 `infrastructure`
|
|
|
|
|
|
|
|
|
|
|
|
`infrastructure` 只负责“怎么接某个外部系统”,不负责“业务上应该先做什么后做什么”。例如:
|
|
|
|
|
|
|
|
|
|
|
|
- MinIO adapter 负责上传和下载文件
|
|
|
|
|
|
- Milvus adapter 负责 upsert/search/delete
|
|
|
|
|
|
- Qwen / DeepSeek adapter 负责生成回答
|
|
|
|
|
|
- Aliyun parser adapter 负责把解析结果映射成统一 `ParsedDocument`
|
|
|
|
|
|
|
|
|
|
|
|
### 5.5 `shared`
|
|
|
|
|
|
|
|
|
|
|
|
`shared` 只放横切能力。任何和文档 ingest、检索、问答编排直接相关的业务逻辑,都不应该放进 `shared`。
|
|
|
|
|
|
|
|
|
|
|
|
## 6. Stable Internal Ports
|
|
|
|
|
|
|
|
|
|
|
|
以下端口是系统内部稳定契约。后续方案替换时,只能替换实现,不允许改动上层 application service 的调用方式,也不允许影响 sibling 模块。
|
|
|
|
|
|
|
|
|
|
|
|
### 6.1 `DocumentRepository`
|
|
|
|
|
|
|
|
|
|
|
|
职责:
|
|
|
|
|
|
|
|
|
|
|
|
- 管理文档元数据
|
|
|
|
|
|
- 管理文档状态
|
|
|
|
|
|
- 管理统计字段,例如 chunk 数、索引状态、摘要状态
|
|
|
|
|
|
|
|
|
|
|
|
说明:
|
|
|
|
|
|
|
|
|
|
|
|
- 列表和状态查询应以 `DocumentRepository` 为主,而不是直接遍历对象存储。
|
|
|
|
|
|
|
|
|
|
|
|
### 6.2 `DocumentBinaryStore`
|
|
|
|
|
|
|
|
|
|
|
|
职责:
|
|
|
|
|
|
|
|
|
|
|
|
- 保存原始文件
|
|
|
|
|
|
- 下载原始文件
|
|
|
|
|
|
- 删除原始文件
|
|
|
|
|
|
- 处理对象存储相关细节
|
|
|
|
|
|
|
|
|
|
|
|
说明:
|
|
|
|
|
|
|
|
|
|
|
|
- 替换 MinIO 或对象存储方案时,只替换该实现。
|
|
|
|
|
|
|
|
|
|
|
|
### 6.3 `DocumentParser`
|
|
|
|
|
|
|
|
|
|
|
|
职责:
|
|
|
|
|
|
|
|
|
|
|
|
- 输入原始文件
|
|
|
|
|
|
- 输出统一结构化解析结果
|
|
|
|
|
|
|
|
|
|
|
|
说明:
|
|
|
|
|
|
|
|
|
|
|
|
- 本地 PDF/MinerU 或阿里云解析只能作为实现差异,不能外溢到业务流程层。
|
|
|
|
|
|
|
|
|
|
|
|
### 6.4 `ChunkBuilder`
|
|
|
|
|
|
|
|
|
|
|
|
职责:
|
|
|
|
|
|
|
|
|
|
|
|
- 输入统一解析结果
|
|
|
|
|
|
- 输出统一 chunk 模型
|
|
|
|
|
|
|
|
|
|
|
|
说明:
|
|
|
|
|
|
|
|
|
|
|
|
- chunk 规则变化只能影响该端口实现,不应影响 retrieval、agent 或 API。
|
|
|
|
|
|
|
|
|
|
|
|
### 6.5 `EmbeddingProvider`
|
|
|
|
|
|
|
|
|
|
|
|
职责:
|
|
|
|
|
|
|
|
|
|
|
|
- 输入文本列表
|
|
|
|
|
|
- 输出 embedding 向量结果
|
|
|
|
|
|
|
|
|
|
|
|
说明:
|
|
|
|
|
|
|
|
|
|
|
|
- 从本地模型切到 OpenAI-compatible embedding,只替换该实现。
|
|
|
|
|
|
|
|
|
|
|
|
### 6.6 `VectorIndex`
|
|
|
|
|
|
|
|
|
|
|
|
职责:
|
|
|
|
|
|
|
|
|
|
|
|
- upsert chunks
|
|
|
|
|
|
- delete by document
|
|
|
|
|
|
- search by query vector
|
|
|
|
|
|
- 管理索引内部 schema
|
|
|
|
|
|
|
|
|
|
|
|
说明:
|
|
|
|
|
|
|
|
|
|
|
|
- Milvus schema 或向量库替换,只能影响该层。
|
|
|
|
|
|
|
|
|
|
|
|
### 6.7 `Retriever`
|
|
|
|
|
|
|
|
|
|
|
|
职责:
|
|
|
|
|
|
|
|
|
|
|
|
- 基于 query、filter、top_k 返回统一检索结果
|
|
|
|
|
|
|
|
|
|
|
|
说明:
|
|
|
|
|
|
|
|
|
|
|
|
- `Retriever` 是业务侧的检索端口,不应再直接持有 embedder、Milvus lifecycle 和 provider-specific 逻辑。
|
|
|
|
|
|
|
|
|
|
|
|
### 6.8 `AnswerGenerator`
|
|
|
|
|
|
|
|
|
|
|
|
职责:
|
|
|
|
|
|
|
|
|
|
|
|
- 基于 query 与 context 生成最终回答
|
|
|
|
|
|
- 屏蔽具体 LLM provider 差异
|
|
|
|
|
|
|
|
|
|
|
|
说明:
|
|
|
|
|
|
|
|
|
|
|
|
- DeepSeek、Qwen 或其他模型切换时,只替换该实现。
|
|
|
|
|
|
|
|
|
|
|
|
### 6.9 `ConversationStore`
|
|
|
|
|
|
|
|
|
|
|
|
职责:
|
|
|
|
|
|
|
|
|
|
|
|
- 创建和读取 session
|
|
|
|
|
|
- 持久化消息历史
|
|
|
|
|
|
- 管理会话生命周期
|
|
|
|
|
|
|
|
|
|
|
|
说明:
|
|
|
|
|
|
|
|
|
|
|
|
- 从内存实现切到 Redis 或数据库实现时,只替换该实现。
|
|
|
|
|
|
|
|
|
|
|
|
## 7. Application Services
|
|
|
|
|
|
|
|
|
|
|
|
### 7.1 `DocumentCommandService`
|
|
|
|
|
|
|
|
|
|
|
|
职责:
|
|
|
|
|
|
|
|
|
|
|
|
- 接收文档上传命令
|
|
|
|
|
|
- 生成 `doc_id`
|
|
|
|
|
|
- 保存原始文件
|
|
|
|
|
|
- 触发解析、分块、向量化、入库
|
|
|
|
|
|
- 更新文档状态和统计信息
|
|
|
|
|
|
- 返回最终处理结果
|
|
|
|
|
|
|
|
|
|
|
|
说明:
|
|
|
|
|
|
|
|
|
|
|
|
- 当前 `DocumentProcessor` 的“流程编排”职责在目标态应迁移到这里。
|
|
|
|
|
|
- parser、chunker、embedder、vector index 的具体实现不应继续塞进一个大类里统一管理。
|
|
|
|
|
|
|
|
|
|
|
|
### 7.2 `DocumentQueryService`
|
|
|
|
|
|
|
|
|
|
|
|
职责:
|
|
|
|
|
|
|
|
|
|
|
|
- 文档列表
|
|
|
|
|
|
- 文档下载
|
|
|
|
|
|
- 文档状态查询
|
|
|
|
|
|
- 文档管理视图查询
|
|
|
|
|
|
|
|
|
|
|
|
说明:
|
|
|
|
|
|
|
|
|
|
|
|
- 列表和状态查询应基于 `DocumentRepository`
|
|
|
|
|
|
- 下载应通过 `DocumentBinaryStore`
|
|
|
|
|
|
- 不再依赖 MinIO 对象结构作为业务视图主来源
|
|
|
|
|
|
|
|
|
|
|
|
### 7.3 `KnowledgeRetrievalService`
|
|
|
|
|
|
|
|
|
|
|
|
职责:
|
|
|
|
|
|
|
|
|
|
|
|
- 对外提供统一检索能力
|
|
|
|
|
|
- 管理 retrieval query 到 retrieval result 的业务转换
|
|
|
|
|
|
- 被 `/knowledge/*` 和 Agent workflow 共用
|
|
|
|
|
|
|
|
|
|
|
|
说明:
|
|
|
|
|
|
|
|
|
|
|
|
- 当前 `knowledge` 与 `agent` 必须统一依赖这一层,不允许各自再维护一套检索流程。
|
|
|
|
|
|
|
|
|
|
|
|
### 7.4 `AgentConversationService`
|
|
|
|
|
|
|
|
|
|
|
|
职责:
|
|
|
|
|
|
|
|
|
|
|
|
- 统一管理问答 workflow
|
|
|
|
|
|
- 读取或创建会话
|
|
|
|
|
|
- 调用 `KnowledgeRetrievalService`
|
|
|
|
|
|
- 构建问答上下文
|
|
|
|
|
|
- 调用 `AnswerGenerator`
|
|
|
|
|
|
- 保存回答和引用来源
|
|
|
|
|
|
|
|
|
|
|
|
说明:
|
|
|
|
|
|
|
|
|
|
|
|
- 当前 `QAAgent` 的 workflow 编排职责在目标态应迁移到这里,或被其吸收后只保留 façade 角色。
|
|
|
|
|
|
- SSE 与普通问答必须共用这一层,不允许复制业务编排逻辑。
|
|
|
|
|
|
|
2026-05-18 16:32:42 +08:00
|
|
|
|
### 7.5 `AgentSessionService`
|
|
|
|
|
|
|
|
|
|
|
|
职责:
|
|
|
|
|
|
|
|
|
|
|
|
- 读取会话详情
|
|
|
|
|
|
- 读取会话历史
|
|
|
|
|
|
- 列出会话
|
|
|
|
|
|
- 删除会话
|
|
|
|
|
|
- 记录会话反馈
|
|
|
|
|
|
|
|
|
|
|
|
说明:
|
|
|
|
|
|
|
|
|
|
|
|
- 这类能力目前仍散落在 `backend/app/api/routes/agent.py`,并直接依赖 `ConversationStore`。
|
|
|
|
|
|
- 目标态应把它们收口到 application 层,保证 API 只处理 transport concerns,而不是继续承担 session 级业务访问。
|
|
|
|
|
|
|
2026-05-18 13:29:57 +08:00
|
|
|
|
## 8. Core Workflows
|
|
|
|
|
|
|
|
|
|
|
|
### 8.1 文档上传入库链路
|
|
|
|
|
|
|
|
|
|
|
|
目标流程如下:
|
|
|
|
|
|
|
|
|
|
|
|
1. `api/documents` 接收上传请求并完成输入校验。
|
|
|
|
|
|
2. `DocumentCommandService` 生成 `doc_id`,初始化文档记录和状态。
|
|
|
|
|
|
3. `DocumentBinaryStore` 保存原始文件。
|
|
|
|
|
|
4. `DocumentParser` 对原始文件执行解析,输出统一结构化结果。
|
|
|
|
|
|
5. `ChunkBuilder` 将解析结果转换为统一 chunk 集合。
|
|
|
|
|
|
6. `EmbeddingProvider` 为 chunks 生成向量。
|
|
|
|
|
|
7. `VectorIndex` 将 chunks 与 vectors 写入索引。
|
|
|
|
|
|
8. `DocumentRepository` 更新文档状态、chunk 数量、索引状态、元数据。
|
|
|
|
|
|
9. API 返回处理结果。
|
|
|
|
|
|
|
|
|
|
|
|
约束:
|
|
|
|
|
|
|
|
|
|
|
|
- 上传处理链路的主编排必须只存在于 `DocumentCommandService`
|
|
|
|
|
|
- 不允许再由 route 或基础设施类直接组织全流程
|
|
|
|
|
|
|
|
|
|
|
|
### 8.2 文档查询链路
|
|
|
|
|
|
|
|
|
|
|
|
目标流程如下:
|
|
|
|
|
|
|
|
|
|
|
|
1. `api/documents` 调用 `DocumentQueryService`
|
|
|
|
|
|
2. 文档列表与状态查询通过 `DocumentRepository`
|
|
|
|
|
|
3. 文档下载通过 `DocumentBinaryStore`
|
|
|
|
|
|
4. 对象存储命名规则只作为实现细节,不作为最终业务真相
|
|
|
|
|
|
|
|
|
|
|
|
约束:
|
|
|
|
|
|
|
|
|
|
|
|
- 文档“存在、状态、统计信息”必须有稳定元数据模型
|
|
|
|
|
|
- 不允许继续通过对象存储遍历结果拼出全部业务语义
|
|
|
|
|
|
|
|
|
|
|
|
### 8.3 Agent 问答链路
|
|
|
|
|
|
|
|
|
|
|
|
目标流程如下:
|
|
|
|
|
|
|
|
|
|
|
|
1. `api/agent` 接收问答请求
|
|
|
|
|
|
2. `AgentConversationService` 读取或创建 session
|
|
|
|
|
|
3. `KnowledgeRetrievalService` 统一执行检索
|
|
|
|
|
|
4. `AnswerGenerator` 基于 query 和 retrieval context 生成回答
|
|
|
|
|
|
5. `ConversationStore` 保存消息历史和引用来源
|
|
|
|
|
|
6. API 将结果以普通 JSON 或 SSE 格式输出
|
|
|
|
|
|
|
|
|
|
|
|
约束:
|
|
|
|
|
|
|
|
|
|
|
|
- 普通问答和 SSE 问答只允许输出形式不同
|
|
|
|
|
|
- 业务编排链必须完全复用
|
|
|
|
|
|
- 检索能力必须来自同一 `KnowledgeRetrievalService`
|
|
|
|
|
|
|
2026-05-18 16:32:42 +08:00
|
|
|
|
### 8.4 Agent Session 管理链路
|
|
|
|
|
|
|
|
|
|
|
|
目标流程如下:
|
|
|
|
|
|
|
|
|
|
|
|
1. `api/agent/session/*` 或 `api/agent/sessions` 接收会话管理请求
|
|
|
|
|
|
2. `AgentSessionService` 读取、列出、删除或归档反馈
|
|
|
|
|
|
3. `ConversationStore` 负责持久化与会话生命周期细节
|
|
|
|
|
|
4. API 输出 session 详情、历史、列表或反馈结果
|
|
|
|
|
|
|
|
|
|
|
|
约束:
|
|
|
|
|
|
|
|
|
|
|
|
- 会话详情、历史、列表和反馈接口不属于问答编排本身,应由独立的 session application service 处理。
|
|
|
|
|
|
- 这部分能力不应继续直接暴露 `ConversationStore` 给路由层。
|
|
|
|
|
|
|
2026-05-18 13:29:57 +08:00
|
|
|
|
## 9. Dependency Rules
|
|
|
|
|
|
|
|
|
|
|
|
系统内部依赖方向固定如下:
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
api -> application -> domain
|
2026-05-18 16:32:42 +08:00
|
|
|
|
application -> domain ports
|
|
|
|
|
|
composition root -> application + infrastructure bindings
|
2026-05-18 13:29:57 +08:00
|
|
|
|
infrastructure -> external systems
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
具体规则如下:
|
|
|
|
|
|
|
|
|
|
|
|
- `api` 可以依赖 `application` 和 API 自己的 request/response models
|
2026-05-18 16:32:42 +08:00
|
|
|
|
- `application` 只能依赖 `domain`、端口接口,以及通过 composition root 注入进来的实现实例
|
2026-05-18 13:29:57 +08:00
|
|
|
|
- `domain` 不能依赖 `api` 或 `infrastructure`
|
|
|
|
|
|
- `infrastructure` 可以依赖 `domain` 定义的端口和数据模型,但不能反向驱动 application 逻辑
|
|
|
|
|
|
|
2026-05-18 16:32:42 +08:00
|
|
|
|
说明:
|
|
|
|
|
|
|
|
|
|
|
|
- 端口绑定发生在 `shared/bootstrap.py` 这类 composition root,而不是 application 层内部。
|
|
|
|
|
|
- application 只能接收已装配好的依赖实例,不能反向“依赖某个 infrastructure 实现类”作为其设计前提。
|
|
|
|
|
|
|
2026-05-18 13:29:57 +08:00
|
|
|
|
## 10. Migration Mapping From Current Code
|
|
|
|
|
|
|
|
|
|
|
|
当前关键代码到目标模块的映射如下。
|
|
|
|
|
|
|
2026-05-18 16:32:42 +08:00
|
|
|
|
### 10.0 旧 facade 存续策略
|
|
|
|
|
|
|
|
|
|
|
|
当前仓库里仍保留若干旧 facade / 兼容入口,包括但不限于:
|
|
|
|
|
|
|
|
|
|
|
|
- `backend/app/services/document_processor.py`
|
|
|
|
|
|
- `backend/app/services/rag/retriever.py`
|
|
|
|
|
|
- `backend/app/services/agent/qa_agent.py`
|
|
|
|
|
|
|
|
|
|
|
|
这些类的定位是“兼容现有调用方的过渡入口”,不是目标态的主要编排层。它们在迁移完成前应继续保留,直到对应的调用方、测试和 API 入口都迁移到 application service 或新的适配层为止。
|
|
|
|
|
|
|
|
|
|
|
|
约束:
|
|
|
|
|
|
|
|
|
|
|
|
- 不允许因为目标架构已经定义,就在没有替换调用方的情况下直接删除这些 facade。
|
|
|
|
|
|
- 任何清理旧 `services` 的动作,都必须先确认是否还存在非 HTTP 的内部调用、测试依赖或临时适配依赖。
|
|
|
|
|
|
- 这些 facade 可以逐步瘦身,但在迁移窗口内应优先保持行为兼容,而不是追求“文件级清零”。
|
|
|
|
|
|
|
2026-05-18 13:29:57 +08:00
|
|
|
|
### 10.1 文档处理
|
|
|
|
|
|
|
|
|
|
|
|
当前:
|
|
|
|
|
|
|
|
|
|
|
|
- `backend/app/services/document_processor.py`
|
|
|
|
|
|
|
|
|
|
|
|
目标:
|
|
|
|
|
|
|
|
|
|
|
|
- 其流程编排职责迁移到 `application/documents/DocumentCommandService`
|
|
|
|
|
|
- 解析、分块、向量、入库分别通过端口接入
|
|
|
|
|
|
- 检索入口从该类中剥离,不再由 ingest orchestration 承担 search 职责
|
2026-05-18 16:32:42 +08:00
|
|
|
|
- 迁移期间该类作为兼容 facade 保留,直到所有直接调用点收口完成
|
2026-05-18 13:29:57 +08:00
|
|
|
|
|
|
|
|
|
|
### 10.2 检索
|
|
|
|
|
|
|
|
|
|
|
|
当前:
|
|
|
|
|
|
|
|
|
|
|
|
- `backend/app/services/rag/retriever.py`
|
|
|
|
|
|
|
|
|
|
|
|
目标:
|
|
|
|
|
|
|
|
|
|
|
|
- `domain/retrieval` 中定义 `Retriever` 端口和统一检索结果模型
|
|
|
|
|
|
- `infrastructure/vectorstore` 中承载具体检索实现
|
|
|
|
|
|
- `application/knowledge/KnowledgeRetrievalService` 作为统一检索用例入口
|
2026-05-18 16:32:42 +08:00
|
|
|
|
- 迁移期间该类作为兼容 facade 保留,避免影响现有检索调用方
|
2026-05-18 13:29:57 +08:00
|
|
|
|
|
|
|
|
|
|
### 10.3 Agent Workflow
|
|
|
|
|
|
|
|
|
|
|
|
当前:
|
|
|
|
|
|
|
|
|
|
|
|
- `backend/app/services/agent/qa_agent.py`
|
|
|
|
|
|
|
|
|
|
|
|
目标:
|
|
|
|
|
|
|
|
|
|
|
|
- workflow 编排职责迁移到 `application/agent/AgentConversationService`
|
|
|
|
|
|
- 具体 LLM 调用走 `AnswerGenerator`
|
|
|
|
|
|
- 具体 session 读写走 `ConversationStore`
|
|
|
|
|
|
- 检索统一走 `KnowledgeRetrievalService`
|
2026-05-18 16:32:42 +08:00
|
|
|
|
- 迁移期间该类作为兼容 facade 保留,避免影响现有问答和测试调用点
|
2026-05-18 13:29:57 +08:00
|
|
|
|
|
|
|
|
|
|
### 10.4 存储
|
|
|
|
|
|
|
|
|
|
|
|
当前:
|
|
|
|
|
|
|
|
|
|
|
|
- `backend/app/services/storage/minio_client.py`
|
|
|
|
|
|
- `backend/app/services/storage/milvus_client.py`
|
|
|
|
|
|
|
|
|
|
|
|
目标:
|
|
|
|
|
|
|
|
|
|
|
|
- MinIO 迁移到 `infrastructure/storage`
|
|
|
|
|
|
- Milvus 迁移到 `infrastructure/vectorstore`
|
|
|
|
|
|
|
|
|
|
|
|
### 10.5 解析
|
|
|
|
|
|
|
|
|
|
|
|
当前:
|
|
|
|
|
|
|
|
|
|
|
|
- `backend/app/services/parser/*`
|
|
|
|
|
|
- `backend/app/services/parser/mineru_parser.py`
|
|
|
|
|
|
|
|
|
|
|
|
目标:
|
|
|
|
|
|
|
|
|
|
|
|
- 全部迁移到 `infrastructure/parser`
|
|
|
|
|
|
- 对外只暴露统一 `DocumentParser` 端口实现
|
|
|
|
|
|
|
|
|
|
|
|
### 10.6 向量化
|
|
|
|
|
|
|
|
|
|
|
|
当前:
|
|
|
|
|
|
|
|
|
|
|
|
- `backend/app/services/embedding/*`
|
|
|
|
|
|
|
|
|
|
|
|
目标:
|
|
|
|
|
|
|
|
|
|
|
|
- 迁移到 `infrastructure/embedding`
|
|
|
|
|
|
- 对外只暴露统一 `EmbeddingProvider`
|
|
|
|
|
|
|
|
|
|
|
|
### 10.7 LLM
|
|
|
|
|
|
|
|
|
|
|
|
当前:
|
|
|
|
|
|
|
|
|
|
|
|
- `backend/app/services/llm/*`
|
|
|
|
|
|
|
|
|
|
|
|
目标:
|
|
|
|
|
|
|
|
|
|
|
|
- 迁移到 `infrastructure/llm`
|
|
|
|
|
|
- 由 `AnswerGenerator` 屏蔽 provider 差异
|
|
|
|
|
|
|
|
|
|
|
|
### 10.8 会话
|
|
|
|
|
|
|
|
|
|
|
|
当前:
|
|
|
|
|
|
|
|
|
|
|
|
- `backend/app/services/agent/session_manager.py`
|
|
|
|
|
|
|
|
|
|
|
|
目标:
|
|
|
|
|
|
|
|
|
|
|
|
- 迁移到 `infrastructure/session`
|
|
|
|
|
|
- 对外通过 `ConversationStore` 暴露
|
|
|
|
|
|
|
|
|
|
|
|
### 10.9 API 模型与内部模型
|
|
|
|
|
|
|
|
|
|
|
|
当前:
|
|
|
|
|
|
|
|
|
|
|
|
- `backend/app/api/models/*`
|
|
|
|
|
|
- `backend/app/schemas/*`
|
|
|
|
|
|
|
|
|
|
|
|
目标:
|
|
|
|
|
|
|
|
|
|
|
|
- 对外 request/response model 保留在 `api`
|
|
|
|
|
|
- 内部 DTO / VO / domain object 收敛到 `application` 或 `domain`
|
|
|
|
|
|
- 不允许 API model 直接渗透到 domain
|
|
|
|
|
|
|
|
|
|
|
|
## 11. Technology Replacement Boundaries
|
|
|
|
|
|
|
|
|
|
|
|
### 11.1 本地解析 / MinerU -> 阿里云文档解析
|
|
|
|
|
|
|
|
|
|
|
|
替换原则:
|
|
|
|
|
|
|
|
|
|
|
|
- 只替换 `DocumentParser` adapter
|
|
|
|
|
|
- `DocumentCommandService` 不应感知解析提供商差异
|
|
|
|
|
|
- `ChunkBuilder` 只接收统一解析结果模型
|
|
|
|
|
|
|
|
|
|
|
|
### 11.2 BGE-M3 -> OpenAI-compatible embedding
|
|
|
|
|
|
|
|
|
|
|
|
替换原则:
|
|
|
|
|
|
|
|
|
|
|
|
- 只替换 `EmbeddingProvider`
|
|
|
|
|
|
- `KnowledgeRetrievalService` 与 `DocumentCommandService` 不应感知 embedding 来源变化
|
|
|
|
|
|
|
|
|
|
|
|
### 11.3 Milvus `1024 + sparse` -> `1536 dense-only`
|
|
|
|
|
|
|
|
|
|
|
|
替换原则:
|
|
|
|
|
|
|
|
|
|
|
|
- 只替换 `VectorIndex` 实现
|
|
|
|
|
|
- collection schema、index 参数、dense-only search 属于 index 内部实现细节
|
|
|
|
|
|
- 上层 retrieval 和 agent workflow 不应因为 schema 变化而改业务接口
|
|
|
|
|
|
|
|
|
|
|
|
### 11.4 DeepSeek / Qwen 切换
|
|
|
|
|
|
|
|
|
|
|
|
替换原则:
|
|
|
|
|
|
|
|
|
|
|
|
- 只替换 `AnswerGenerator` 背后的 provider adapter
|
|
|
|
|
|
- 上层 conversation workflow 不应直接依赖具体模型 SDK
|
|
|
|
|
|
|
|
|
|
|
|
### 11.5 内存 session -> Redis / DB session
|
|
|
|
|
|
|
|
|
|
|
|
替换原则:
|
|
|
|
|
|
|
|
|
|
|
|
- 只替换 `ConversationStore`
|
|
|
|
|
|
- API 和 application service 不应感知 session 持久化细节
|
|
|
|
|
|
|
|
|
|
|
|
## 12. Guardrails
|
|
|
|
|
|
|
|
|
|
|
|
后续所有 backend 重构和新增功能必须遵守以下规则:
|
|
|
|
|
|
|
|
|
|
|
|
- 禁止 `api/routes` 直接实例化 parser、embedder、Milvus、MinIO、LLM client
|
|
|
|
|
|
- 禁止 `application` 层直接 import 第三方 SDK
|
|
|
|
|
|
- 禁止 `domain` 层依赖 FastAPI、Pydantic route model、MinIO SDK、Milvus SDK、LLM SDK
|
|
|
|
|
|
- 禁止 SSE 和普通问答各自维护独立 workflow
|
|
|
|
|
|
- 禁止把对象存储命名规则作为唯一业务元数据来源
|
|
|
|
|
|
- 禁止新建第二个“大一统流程类”替代 `DocumentProcessor`
|
|
|
|
|
|
- 禁止 `knowledge` 和 `agent` 各自维护独立检索实现
|
|
|
|
|
|
- 禁止 parser、embedding、vector index、llm provider 的替换穿透到 API 层
|
|
|
|
|
|
|
|
|
|
|
|
## 13. Architecture Review Checklist
|
|
|
|
|
|
|
|
|
|
|
|
后续评审和重构验收时,至少核对以下问题:
|
|
|
|
|
|
|
|
|
|
|
|
1. 上传、下载、列表、解析、切片、向量、入库、检索、Agent Workflow 是否都映射到了明确模块。
|
|
|
|
|
|
2. 系统是否仍保持单服务,而不是被动演化成伪微服务结构。
|
|
|
|
|
|
3. 是否存在唯一、清晰的目标目录结构。
|
|
|
|
|
|
4. 是否定义了稳定端口列表。
|
|
|
|
|
|
5. 是否定义了文档上传入库、文档查询、Agent 问答三条核心 workflow。
|
|
|
|
|
|
6. 是否定义了单向依赖方向。
|
|
|
|
|
|
7. 是否明确列出了架构禁令。
|
|
|
|
|
|
8. 是否定义了当前关键代码到目标模块的映射。
|
|
|
|
|
|
9. 是否明确定义了 parser、embedding、vector index、LLM、session store 的替换边界。
|
|
|
|
|
|
10. 是否明确 `knowledge` 与 `agent` 共用同一 retrieval 底座。
|
|
|
|
|
|
11. 是否明确 API 层只负责 transport concerns,不再直接承担业务编排。
|
|
|
|
|
|
12. 是否保证后续替换方案时,上层 application service 与外部 API 契约不被迫变化。
|