三层架构设计:Controller → Service → Core
学习目标
- 理解 Dify 的三层架构划分原则和各层职责
- 掌握 Flask-RESTX 与 FastOpenAPI 双框架共存的设计决策
- 学会分析租户隔离贯穿所有层次的数据流
前置知识
无特定前置知识。本章聚焦 Dify 自身的架构设计。
项目实践
三层架构概览
Controller 层:路由与输入校验
Controller 负责:
- 路由定义:使用 Flask-RESTX 或 FastOpenAPI 声明端点
- 输入解析:通过 Pydantic 模型校验请求参数
- 响应序列化:调用 Service 后序列化返回数据
两种框架共存:
| 框架 | 适用场景 | 位置 |
|---|---|---|
| Flask-RESTX | 成熟稳定的 API 端点 | api/controllers/console/、api/controllers/service_api/ |
| FastOpenAPI | 新增 API 端点 | api/controllers/ 中使用 fastopenapi 声明式路由 |
# 伪代码,展示 Controller 职责class WorkflowAPI: @api.route("/workflows/<uuid:workflow_id>") def get_workflow(self, workflow_id): # 1. 解析输入(路径参数、查询参数) # 2. 调用 Service 层 workflow = workflow_service.get_workflow(workflow_id, tenant_id) # 3. 序列化返回 return jsonify(dump_response(WorkflowDTO, workflow))Controller 层不做什么:
- 不包含业务逻辑
- 不直接操作数据库
- 不实现领域规则
Service 层:业务逻辑协调
Service 是 Controller 和 Core 之间的桥梁:
# 伪代码,展示 Service 职责class WorkflowService: def get_workflow(self, workflow_id: str, tenant_id: str) -> Workflow: # 1. 协调仓储访问(SQLAlchemy 查询) with Session(db.engine) as session: stmt = select(Workflow).where( Workflow.id == workflow_id, Workflow.tenant_id == tenant_id, # 租户隔离 ) workflow = session.execute(stmt).scalar_one_or_none()
# 2. 调用 Core 层执行领域逻辑 if workflow: workflow_entry = WorkflowEntry(...) result = workflow_entry.run()
# 3. 可能触发异步任务 async_workflow_service.publish_task(...)
return resultService 层的关键职责:
- 仓储协调:通过 SQLAlchemy 访问数据库
- 领域编排:组合多个 Core 模块完成复杂业务
- 任务分发:通过
async_workflow_service发布异步任务 - 租户隔离:所有查询和操作都绑定
tenant_id
Core 层:领域逻辑
Core 层包含纯领域逻辑,不依赖外部基础设施:
| 模块 | 职责 |
|---|---|
core/agent/ | Agent 运行时(Function Calling / ReAct) |
core/workflow/ | 工作流节点定义与执行 |
core/rag/ | RAG 管线(索引 + 检索) |
core/tools/ | 工具系统(发现 + 执行) |
core/model_manager.py | 多模型抽象与负载均衡 |
core/plugin/ | 插件系统 |
core/mcp/ | MCP 协议实现 |
Core 层的设计原则:
- 领域纯粹性:不直接访问 HTTP 请求/响应对象
- 可测试性:领域逻辑可通过单元测试验证
- 依赖注入:通过构造函数注入依赖,而非全局访问
租户隔离贯穿
tenant_id 从 Controller 到 Core 贯穿所有层次:
- Controller:从认证上下文提取
tenant_id - Service:所有数据库查询都加上
tenant_id过滤 - Core:LLM 配额检查、向量数据库操作等都使用
tenant_id隔离
问题与规避
陷阱 1:Controller 中混入业务逻辑
在快速迭代中,容易在 Controller 中写入本应属于 Service 的逻辑。
Dify 的规避策略:
AGENTS.md明确规则:「Controllers: parse input via Pydantic, invoke services, return serialised responses; no business logic」- 代码审查中检查 Controller 行数,过长则提示拆分
陷阱 2:租户隔离遗漏
忘记在查询中添加 tenant_id 过滤可能导致跨租户数据泄露。
Dify 的规避策略:
- SQLAlchemy 模型查询模板中始终包含
tenant_id条件 AGENTS.md规则:「Maintain tenant awareness end-to-end」
陷阱 3:双框架 API 不一致
Flask-RESTX 和 FastOpenAPI 的端点行为可能不一致。
Dify 的规避策略:
- 新 API 统一使用 FastOpenAPI 模式
- 旧 Flask-RESTX API 逐步迁移
- 遵循
controllers/API_SCHEMA_GUIDE.md中的统一规范
设计取舍
Flask-RESTX + FastOpenAPI 共存 vs 统一框架
优势:
- Flask-RESTX 成熟稳定,现有端点无需重写
- FastOpenAPI 提供声明式路由和自动文档,适合新增端点
- 渐进式迁移,不影响线上服务
代价:
- 维护两套框架的依赖和配置
- 开发者需要学习两种模式
- 错误处理和中间件可能不一致
参考来源
- Dify API 模块源码:
api/ - API Schema Guide:
api/controllers/API_SCHEMA_GUIDE.md - Backend Agent Guide:
api/AGENTS.md