跳转到内容

三层架构设计: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 result

Service 层的关键职责

  • 仓储协调:通过 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