Django MVT vs FastAPI DDD 架构区别
文章目录
-
- [Django MVT vs FastAPI DDD 架构区别](#Django MVT vs FastAPI DDD 架构区别)
- [01:Django MVT vs FastAPI ------ "全能坦克"与"极速跑车"的架构对决](#01:Django MVT vs FastAPI —— “全能坦克”与“极速跑车”的架构对决)
-
- [🏛️ 核心哲学对比](#🏛️ 核心哲学对比)
-
- [1. Django: "Batteries Included" (自带电池) -> **MVT 架构**](#1. Django: "Batteries Included" (自带电池) -> MVT 架构)
- [2. FastAPI: "Modern & Fast" (现代与速度) -> **路由 + 依赖注入**](#2. FastAPI: "Modern & Fast" (现代与速度) -> 路由 + 依赖注入)
- [⚔️ 架构组件深度对标](#⚔️ 架构组件深度对标)
- [🔍 关键差异点详解](#🔍 关键差异点详解)
-
- [1. 路由注册方式:分散 vs 集中](#1. 路由注册方式:分散 vs 集中)
- [2. 依赖注入:隐式全局 vs 显式组合](#2. 依赖注入:隐式全局 vs 显式组合)
- [3. 数据验证:运行时 vs 编译时(类型提示)](#3. 数据验证:运行时 vs 编译时(类型提示))
- [4. 异步支持 (Async/Await)](#4. 异步支持 (Async/Await))
- [🧐 架构选择指南:什么时候用哪个?](#🧐 架构选择指南:什么时候用哪个?)
-
- [✅ 选择 Django (MVT) 当:](#✅ 选择 Django (MVT) 当:)
- [✅ 选择 FastAPI 当:](#✅ 选择 FastAPI 当:)
- [💣 常见误区](#💣 常见误区)
- [📝 总结](#📝 总结)
- [02:架构深潜 ------ FastAPI 下的 DDD 与 Django 的 MTV 实战原理](#02:架构深潜 —— FastAPI 下的 DDD 与 Django 的 MTV 实战原理)
-
- [🏛️ 第一部分:Django 的 MTV 架构 ------ 从"大泥球"到"实用主义分层"](#🏛️ 第一部分:Django 的 MTV 架构 —— 从“大泥球”到“实用主义分层”)
-
- [1. 原生 MTV 的核心逻辑](#1. 原生 MTV 的核心逻辑)
- [2. 面对复杂业务时的"痛点"](#2. 面对复杂业务时的“痛点”)
- [3. Django 下的"伪 DDD"演进策略](#3. Django 下的“伪 DDD”演进策略)
- [🚀 第二部分:FastAPI 下的 DDD 架构 ------ 天生一对的"自由组合"](#🚀 第二部分:FastAPI 下的 DDD 架构 —— 天生一对的“自由组合”)
-
- [1. FastAPI 与 DDD 的天然契合点](#1. FastAPI 与 DDD 的天然契合点)
- [2. 标准的 FastAPI + DDD 分层架构原理](#2. 标准的 FastAPI + DDD 分层架构原理)
-
- [A. 领域层 (Domain Layer) ------ 核心中的核心](#A. 领域层 (Domain Layer) —— 核心中的核心)
- [B. 应用层 (Application Layer) ------ 用例编排](#B. 应用层 (Application Layer) —— 用例编排)
- [C. 基础设施层 (Infrastructure Layer) ------ 具体实现](#C. 基础设施层 (Infrastructure Layer) —— 具体实现)
- [D. 表现层 (Presentation / Interface Layer) ------ FastAPI 的主场](#D. 表现层 (Presentation / Interface Layer) —— FastAPI 的主场)
- [3. 依赖流向:箭头向内](#3. 依赖流向:箭头向内)
- [⚔️ 第三部分:深度对比 ------ 两种哲学的碰撞](#⚔️ 第三部分:深度对比 —— 两种哲学的碰撞)
- [💡 核心原理总结](#💡 核心原理总结)
-
- [1. Django 的"实用主义"](#1. Django 的“实用主义”)
- [2. FastAPI 的"自由主义"](#2. FastAPI 的“自由主义”)
- [3. 关键分歧点:贫血模型 vs 充血模型](#3. 关键分歧点:贫血模型 vs 充血模型)
- [📝 结语](#📝 结语)
- [03:实战深潜 ------ 文件上传与异步解析的架构对决](#03:实战深潜 —— 文件上传与异步解析的架构对决)
-
- [🎯 场景定义:核心挑战](#🎯 场景定义:核心挑战)
- [🐘 方案一:Django 架构 (MTV + Celery)](#🐘 方案一:Django 架构 (MTV + Celery))
-
- "重型坦克"模式:稳健、隔离、但略显笨重
- [1. 架构流向](#1. 架构流向)
- [2. 核心组件职责](#2. 核心组件职责)
-
- [A. Model (数据层)](#A. Model (数据层))
- [B. View (控制层)](#B. View (控制层))
- [C. Task (异步层 - Celery)](#C. Task (异步层 - Celery))
- [3. Django 方案的痛点](#3. Django 方案的痛点)
- [⚡ 方案二:FastAPI 架构 (DDD + Native Async)](#⚡ 方案二:FastAPI 架构 (DDD + Native Async))
-
- "极速跑车"模式:轻量、内聚、极致并发
- [1. 架构流向 (轻量级原生方案)](#1. 架构流向 (轻量级原生方案))
- [2. 核心组件职责 (DDD 分层)](#2. 核心组件职责 (DDD 分层))
-
- [A. Domain Layer (领域层)](#A. Domain Layer (领域层))
- [B. Infrastructure Layer (基础设施层)](#B. Infrastructure Layer (基础设施层))
- [C. Interface Layer (FastAPI Router)](#C. Interface Layer (FastAPI Router))
- [3. FastAPI 方案的痛点](#3. FastAPI 方案的痛点)
- [⚔️ 深度对比:架构决策的关键点](#⚔️ 深度对比:架构决策的关键点)
- [💡 最佳实践建议](#💡 最佳实践建议)
-
- [1. 什么时候坚持用 Django + Celery?](#1. 什么时候坚持用 Django + Celery?)
- [2. 什么时候选择 FastAPI + Native Async?](#2. 什么时候选择 FastAPI + Native Async?)
- [3. 混合模式 (The Best of Both Worlds)](#3. 混合模式 (The Best of Both Worlds))
- [📝 总结](#📝 总结)
- [04:混合架构实战 ------ FastAPI + Celery 的"黄金搭档"模式](#04:混合架构实战 —— FastAPI + Celery 的“黄金搭档”模式)
-
- [🏗️ 架构全景图:各司其职](#🏗️ 架构全景图:各司其职)
- [💻 核心代码实现原理](#💻 核心代码实现原理)
-
- [1. 项目结构建议](#1. 项目结构建议)
- [2. 第一步:配置 Celery (`core/celery_app.py`)](#2. 第一步:配置 Celery (
core/celery_app.py)) - [3. 第二步:定义 Celery 任务 (`tasks.py`)](#3. 第二步:定义 Celery 任务 (
tasks.py)) - [4. 第三步:FastAPI 路由层 (`api/routes.py`)](#4. 第三步:FastAPI 路由层 (
api/routes.py))
- [⚙️ 关键难点与解决方案](#⚙️ 关键难点与解决方案)
-
- [1. 异步 (FastAPI) 与 同步 (Celery) 的摩擦](#1. 异步 (FastAPI) 与 同步 (Celery) 的摩擦)
- [2. 文件传递方式](#2. 文件传递方式)
- [3. 状态同步机制](#3. 状态同步机制)
- [4. 优雅关闭与信号处理](#4. 优雅关闭与信号处理)
- [🚀 进阶优化:如何让体验更丝滑?](#🚀 进阶优化:如何让体验更丝滑?)
-
- [1. 实时推送 (WebSocket + Celery)](#1. 实时推送 (WebSocket + Celery))
- [2. 动态扩缩容](#2. 动态扩缩容)
- [3. 依赖注入的妙用](#3. 依赖注入的妙用)
- [📝 总结:为什么这是"Best of Both Worlds"?](#📝 总结:为什么这是“Best of Both Worlds”?)
01:Django MVT vs FastAPI ------ "全能坦克"与"极速跑车"的架构对决
写在前面 :
今天是 2026 年 3 月 15 日。在 Python Web 的生态里,Django 和 FastAPI 无疑是两座高峰。
很多刚学完 Django MVT 模式的同学,转头看到 FastAPI 的代码结构,往往会感到一阵眩晕:"怎么没有 Template 了?View 去哪了?Controller 呢?"
这篇笔记,我们不谈谁好谁坏,只从架构设计哲学 的角度,深度拆解 Django 的 MVT 与 FastAPI 的路由/依赖注入模式 到底有什么本质区别。搞懂了这个,你才能在不同场景下选对框架。
🏛️ 核心哲学对比
1. Django: "Batteries Included" (自带电池) -> MVT 架构
Django 诞生于新闻编辑室,目的是快速发布内容。
- 核心逻辑 :框架帮你把该想的都想好了。它强制你遵循 MVT (Model-View-Template) 模式。
- 特点:强约定、单体化、同步为主(虽已支持异步)、全功能集成。
- 类比 :像一家五星级酒店。你住进去,房间、餐厅、健身房、洗衣服务全都有。你不需要自己带床单,但你也很难把床拆了换成吊床。
2. FastAPI: "Modern & Fast" (现代与速度) -> 路由 + 依赖注入
FastAPI 诞生于微服务和 API 优先的时代,目的是高性能与开发体验。
- 核心逻辑 :框架只提供核心能力(路由、验证、文档),其他全靠你组装。它没有固定的"MVT"说法,通常被称为 Path Operation Functions 配合 Dependency Injection。
- 特点:弱约定、模块化、原生异步、基于类型提示(Type Hints)。
- 类比 :像一套乐高积木 或者赛车改装车间。只给你底盘和引擎(路由和验证),车身、轮胎、内饰你自己选。你可以组装成 F1 赛车,也可以组装成家用轿车,自由度极高,但需要你自己懂搭配。
⚔️ 架构组件深度对标
让我们把两个框架的组件拉出来"排排坐",看看它们是如何对应(或不对应)的。
| 概念 | Django (MVT) | FastAPI (现代 API 风格) | 本质区别 |
|---|---|---|---|
| 数据层 | Model (models.py)内置 ORM,强耦合 Django。 |
Schema / Model (pydantic.BaseModel)通常用 Pydantic 做数据验证,ORM 可选 (SQLAlchemy, Tortoise, Django ORM)。 |
Django Model 既是数据结构又是数据库映射;FastAPI 中数据验证 (Pydantic) 与 数据库操作 (ORM) 是分离的。 |
| 逻辑层 | View (views.py)函数或类,接收 Request,返回 Response。 |
Path Operation Function 直接装饰在函数上 (@app.get),逻辑即路由。 |
Django 的 View 需要先在 urls.py 注册;FastAPI 的路由直接定义在函数上,所见即所得。 |
| 展示层 | Template (templates/)DTL 语法,负责渲染 HTML。 |
无内置模板通常返回 JSON。若需 HTML,需手动集成 Jinja2 (其实 Django 模板也源于此)。 | Django 默认为了服务端渲染 (SSR) ;FastAPI 默认为了前后端分离 (API)。 |
| 请求处理 | Middleware & Context Processors 全局钩子,隐式传递 request。 |
Dependency Injection显式声明依赖,参数自动注入。 | 这是最大的架构差异! Django 隐式,FastAPI 显式。 |
| 文档 | 需第三方库 (如 drf-spectacular) 或手写。 | 自动生成 OpenAPI (Swagger/ReDoc)基于类型提示自动生成。 | FastAPI 将文档作为一等公民,Django 视为附加功能。 |
🔍 关键差异点详解
1. 路由注册方式:分散 vs 集中
-
Django (集中式) :
你需要维护一个
urls.py文件,把 URL 路径映射到 View 函数。python# urls.py path('articles/<int:id>/', views.article_detail, name='detail') # views.py def article_detail(request, id): ...缺点:改个 URL 要动两个文件,容易不同步。
-
FastAPI (分散式/装饰器) :
路由直接挂在函数头上。
python@app.get("/articles/{article_id}") async def read_article(article_id: int): return {"id": article_id}优点:代码在一起,重构方便,类型检查直接生效。
2. 依赖注入:隐式全局 vs 显式组合
这是 FastAPI 最迷人的地方,也是它与 Django 最大的架构不同。
-
Django 的做法 :
你想在 View 里获取当前用户?Django 通过中间件把
user塞进request对象里。pythondef my_view(request): user = request.user # 隐式依赖,你必须知道 request 里有这个如果你想复用一段"检查权限"的逻辑,通常用装饰器 (
@permission_required)。 -
FastAPI 的做法 :
你显式定义一个依赖函数,然后在路由参数里声明它。
python# 定义依赖 async def get_current_user(token: str = Header(...)) -> User: # 解析 token 逻辑 return user # 使用依赖 @app.get("/items/") async def read_items(current_user: User = Depends(get_current_user)): # current_user 自动注入,无需手动调用 return {"owner": current_user.username}优势:
- 测试极其方便:测试时可以轻松 override(覆盖)这个依赖,返回一个假用户。
- 组合性强:可以层层嵌套依赖(A 依赖 B,B 依赖 C),形成清晰的依赖树。
- 文档自动化:FastAPI 能分析这些依赖,自动生成 API 文档中的认证要求。
3. 数据验证:运行时 vs 编译时(类型提示)
-
Django :
主要依赖 Form 类或 Serializer (DRF) 在运行时进行验证。虽然 DRF 很强,但代码量较大,且类型提示支持不如原生好。
-
FastAPI :
基于 Python Type Hints 和 Pydantic。
python# 直接在参数里定义类型和验证规则 @app.post("/items/") async def create_item(item: ItemSchema): # ItemSchema 继承自 Pydantic # 如果请求体不符合 schema,FastAPI 自动返回 422 错误,根本进不到函数内部 return item这种模式让编辑器(IDE)能提供完美的自动补全,并且在代码运行前就能发现很多类型错误。
4. 异步支持 (Async/Await)
-
Django :
虽然 3.0+ 支持异步 View 和 ORM,但由于历史包袱,整个生态(尤其是中间件和第三方库)是混合的。如果在异步 View 里调用了同步的 ORM 方法,会阻塞事件循环。
-
FastAPI :
生而异步 。基于 Starlette 和 Pydantic,全链路原生支持async/await。在高并发 IO 密集型场景(如大量调用外部 API、WebSocket)下,性能表现通常优于 Django。
🧐 架构选择指南:什么时候用哪个?
✅ 选择 Django (MVT) 当:
- 你需要快速构建全栈应用:不仅要有 API,还要有后台管理系统(Admin)、HTML 页面、用户认证、邮件发送。Django 一行命令全搞定。
- 团队偏好"约定大于配置":希望新人上来就有统一的目录结构和编码规范,不想花时间选型各种库。
- 内容驱动型网站:博客、新闻门户、电商、SaaS 平台,业务逻辑复杂且关系紧密。
- 不需要极致的并发:普通的 Web 请求,Django 的性能完全够用。
✅ 选择 FastAPI 当:
- 纯 API 后端:前端是 Vue/React/Mobile,后端只负责吐 JSON。
- 微服务架构:需要将大系统拆分成小服务,每个服务轻量、独立部署。
- 高并发/实时应用:聊天室、即时推送、大量调用第三方 AI 接口等 IO 密集型任务。
- 机器学习/AI 服务:Python 是 AI 的首选,FastAPI 能很好地封装模型推理接口,且性能损耗小。
- 对类型安全和自动文档有强需求。
💣 常见误区
-
"FastAPI 没有 MVC/MVT,所以它没有架构"
- 真相 :FastAPI 只是没有强制你使用某种架构。你可以在 FastAPI 中手动实现分层架构(Controller-Service-Repository),而且很多人就是这么做的。它给了你自由,但也要求你有更强的架构设计能力。
-
"Django 太慢了,不能用在生产环境"
- 真相:对于 95% 的 Web 应用,瓶颈在数据库和网络,而不是框架本身的几毫秒差异。Django 的异步化进展很快,且配合缓存、CDN 后,性能完全不是问题。
-
"学了 FastAPI 就不用学 Django ORM 了"
- 真相:FastAPI 本身不带 ORM。你依然需要学习 SQLAlchemy 或 Tortoise ORM,甚至可以在 FastAPI 里直接使用 Django ORM(虽然不常见)。Django ORM 的设计思想依然值得学习。
📝 总结
- Django MVT 是Opinionated (有主见) 的。它告诉你:"照我说的做,我能保你衣食无忧。"适合大而全的系统。
- FastAPI 是Unopinionated (随和) 的。它告诉你:"我给你最快的引擎和最好的工具,车怎么造看你。"适合小而美 或高并发的 API 服务。
在 2026 年的今天,两者并不是非此即彼的关系。很多大型架构甚至是 Django 做主业务和管理后台 + FastAPI 做高性能微服务 的混合模式。理解它们的架构差异,能让你在技术选型时更加游刃有余。
02:架构深潜 ------ FastAPI 下的 DDD 与 Django 的 MTV 实战原理
写在前面 :
接上一讲,我们对比了 Django 的 MVT 和 FastAPI 的灵活路由。但很多同学在真正落地大型项目时会发现:框架只是骨架,架构才是灵魂。
简单的 CRUD(增删改查)用哪个框架都差不多,可一旦业务逻辑复杂到像"电商交易链路"或"金融风控系统",代码结构就会决定项目的生死。
今天这一讲,我们不写一行具体代码,而是从设计原理 和组织哲学层面,深度剖析:
- Django 是如何在 MTV(Model-Template-View) 模式下演进以应对复杂业务的?
- FastAPI 是如何天然契合 DDD(领域驱动设计) 思想的?
- 两者在分层策略 、依赖方向 和业务逻辑归属上的本质区别。
🏛️ 第一部分:Django 的 MTV 架构 ------ 从"大泥球"到"实用主义分层"
Django 官方推崇的是 MTV (Model-Template-View),这其实是 MVC 的变种。但在 DDD(领域驱动设计)的语境下,原生 Django 结构往往面临挑战。
1. 原生 MTV 的核心逻辑
- Model (模型) : 数据的唯一真理。包含数据库字段定义、基础验证逻辑,甚至部分业务方法(如
user.has_permission())。 - Template (模板): 纯展示层,负责 HTML 渲染。
- View (视图): 控制器角色。接收请求,调用 Model,选择 Template。
2. 面对复杂业务时的"痛点"
在 DDD 理念中,我们希望**业务逻辑(Domain Logic)**独立于框架(Framework Agnostic)。
但在原生 Django 中:
- Model 太重:为了复用逻辑,开发者倾向于把所有业务规则都塞进 Model 方法里,导致 Model 变得臃肿不堪,且强耦合 Django ORM。
- View 太乱:复杂的业务流程(如"下单 -> 扣库存 -> 计算优惠 -> 创建订单 -> 发送通知")如果全写在 View 函数里,会导致 View 变成几千行的"上帝函数"。
- 测试困难:因为业务逻辑散落在 Model 和 View 中,且深度依赖 Django 的环境(Request, ORM),单元测试很难在不启动 Django 环境的情况下运行。
3. Django 下的"伪 DDD"演进策略
虽然 Django 不强制 DDD,但社区在大型项目中形成了一套约定俗成的分层模式,用来在 MTV 框架内实现 DDD 的思想:
-
引入 Service 层 (服务层) :
将复杂的业务逻辑从 View 和 Model 中剥离出来,放入
services.py或services/目录。View 只负责流程调度,Service 负责核心业务规则。 -
引入 Domain 层 (领域层) :
在极复杂的项目中,会单独创建一个
domain/app。这里存放纯粹的 Python 类(实体 Entity、值对象 Value Object),完全不继承django.db.models.Model。- 原理:核心业务逻辑操作这些纯 Python 对象。
- 适配 :通过 Repository 模式(通常在
repositories.py)将这些纯对象与 Django ORM 模型进行转换。
-
依赖倒置的妥协 :
原生 Django 是"自底向上"的(Model 定义好,View 去用)。而在 DDD 实践中,需要人为地通过接口(Interface/Abstract Base Class)来解耦,让上层业务不直接依赖下层的具体 ORM 实现。
核心总结 :Django 的 MTV 是一种框架优先 的架构。要在其中实现 DDD,需要开发者主动克制框架的便利性,人为地增加抽象层(Service, Repository, Domain Entities),这是一种"带着镣铐跳舞"的艺术。
🚀 第二部分:FastAPI 下的 DDD 架构 ------ 天生一对的"自由组合"
FastAPI 本身没有预设的架构模式(没有强制的 Model 或 View 概念),这反而让它成为了实施 DDD (领域驱动设计) 的绝佳土壤。
1. FastAPI 与 DDD 的天然契合点
DDD 的核心诉求是:业务逻辑核心化,基础设施边缘化 。
FastAPI 的两大特性完美支持这一点:
- 依赖注入 (Dependency Injection):可以清晰地界定"基础设施"(如数据库会话、当前用户、外部 API 客户端)和"业务逻辑"。
- Pydantic 模型:作为数据传输对象 (DTO),与领域模型 (Domain Model) 天然分离。
2. 标准的 FastAPI + DDD 分层架构原理
在 FastAPI 实践中,通常会严格遵循 整洁架构 (Clean Architecture) 或 六边形架构 (Hexagonal Architecture),分为以下四层:
A. 领域层 (Domain Layer) ------ 核心中的核心
- 位置 :
src/domain/ - 内容 :
- Entities (实体) : 纯粹的业务对象,包含核心业务规则。不依赖任何第三方库(无 SQLAlchemy, 无 FastAPI, 无 Pydantic)。
- Value Objects (值对象) : 不可变的业务概念(如
EmailAddress,Money)。 - Repository Interfaces (仓储接口) : 只定义抽象方法(如
get_by_id,save),不包含具体实现。 - Domain Services: 跨实体的复杂业务逻辑。
- 原则:这一层是纯 Python 代码,甚至可以脱离 Web 框架独立运行和测试。
B. 应用层 (Application Layer) ------ 用例编排
- 位置 :
src/application/ - 内容 :
- Use Cases (用例) : 定义具体的业务操作(如
CreateOrderUseCase)。 - DTOs: 定义输入输出的数据结构。
- Use Cases (用例) : 定义具体的业务操作(如
- 职责:它调用领域层的实体和接口,编排业务流程,但不关心数据具体存在哪里(MySQL 还是 Mongo)。
C. 基础设施层 (Infrastructure Layer) ------ 具体实现
- 位置 :
src/infrastructure/ - 内容 :
- ORM Models: SQLAlchemy 或 Tortoise 的具体模型。
- Repository Implementations: 实现领域层定义的接口,真正去操作数据库。
- External Services: 调用微信支付的客户端、发送邮件的实现。
- 原则:这一层依赖领域层(依赖倒置),将具体技术细节注入到上层。
D. 表现层 (Presentation / Interface Layer) ------ FastAPI 的主场
- 位置 :
src/api/或main.py - 内容 :
- Routers : 定义 HTTP 路径 (
@app.get)。 - Schemas: Pydantic 模型,用于请求验证和响应序列化。
- Dependencies: 将基础设施层的实例(如 DB Session, Repo Impl)注入到路由函数中。
- Routers : 定义 HTTP 路径 (
- 职责:只负责处理 HTTP 协议、参数验证、异常捕获,然后调用应用层的 Use Case。
3. 依赖流向:箭头向内
在 FastAPI + DDD 架构中,依赖关系是严格单向的:
表现层 -> 应用层 -> 领域层 <- 基础设施层
(注意:基础设施层依赖领域层,而不是反过来)
这意味着:核心业务逻辑(领域层)根本不知道 FastAPI 的存在,也不知道数据库是 MySQL 还是 PostgreSQL。
⚔️ 第三部分:深度对比 ------ 两种哲学的碰撞
| 维度 | Django (MTV + 演进) | FastAPI (DDD 原生风格) |
|---|---|---|
| 架构驱动力 | 框架驱动。结构由 Django 的 App 机制决定。 | 领域驱动。结构由业务边界 (Bounded Context) 决定。 |
| 业务逻辑位置 | 容易分散在 Model 方法和 View 函数中。需刻意提取到 Service 层。 | 严格集中在 Domain Entities 和 Use Cases 中。 |
| 数据模型 | ORM Model 即业务模型。通常混用,难以分离。 | 严格分离。Pydantic (DTO) != SQLAlchemy (DB) != Domain Entity (业务)。 |
| 依赖方向 | 通常是自上而下 (View -> Model)。难以反转。 | 依赖倒置。高层模块不依赖低层模块,都依赖抽象。 |
| 测试性 | 通常需要 pytest-django,加载整个框架环境。 |
核心领域逻辑可纯单元测试,无需任何 Web 或 DB 环境。 |
| 灵活性 | 较低。修改底层存储(如换 NoSQL)成本高,因为 Model 耦合紧。 | 极高。只需替换 Infrastructure 层的实现,核心业务代码无需改动。 |
| 上手难度 | 低。跟着教程做就能跑起来。 | 高。需要理解抽象、接口、依赖注入等设计模式。 |
| 适用规模 | 中小型项目、内容型网站、快速原型。 | 大型复杂系统、微服务、高迭代频率的核心业务。 |
💡 核心原理总结
1. Django 的"实用主义"
Django 的设计初衷是效率。它假设你的业务逻辑和数据库结构是紧密绑定的。
- 优点:开发速度极快,代码量少, conventions(约定)减少了决策成本。
- 代价:随着业务复杂度指数级上升,MTV 的三层结构容易崩塌,需要开发者具备很强的重构能力,人为地引入 DDD 概念来"救火"。
2. FastAPI 的"自由主义"
FastAPI 的设计初衷是性能与规范。它不提供业务结构,只提供工具。
- 优点:天生适合 DDD。你可以从一开始就构建一个清晰、可测试、易维护的领域模型。基础设施的变更不会影响核心业务。
- 代价:你需要自己搭建所有脚手架(目录结构、依赖注入配置、单元边界)。如果没有良好的架构设计能力,很容易把 FastAPI 写成"散弹枪代码"(所有逻辑都在路由函数里)。
3. 关键分歧点:贫血模型 vs 充血模型
- Django 常见模式 :贫血模型 (Anemic Domain Model)。Model 主要是数据容器(Getter/Setter),逻辑都在外部的 Service 或 View 里。
- DDD 理想模式 (FastAPI 易实现) :充血模型 (Rich Domain Model) 。Entity 对象内部包含行为(如
order.cancel()),对象自己维护自己的状态不变性。
📝 结语
- 如果你正在做一个内容管理系统、博客、或者初创公司的 MVP,Django 的 MTV 模式(即使稍微混合一点 Service 层)是最高效的选择。不要为了 DDD 而 DDD,过度设计是万恶之源。
- 如果你正在构建核心交易系统、复杂的 SaaS 平台、或者需要长期演进且业务规则多变的微服务 ,那么采用 FastAPI + 严格 DDD 分层 是更明智的投资。虽然前期搭建成本高,但随着时间推移,其维护成本和扩展优势会呈指数级显现。
架构没有银弹,只有权衡 (Trade-off)。
03:实战深潜 ------ 文件上传与异步解析的架构对决
前情提要 :
在上一篇中,我们厘清了 Django (MTV) 与 FastAPI (DDD/现代架构) 的设计哲学。
理论总是灰色的,而生命之树常青。今天,我们选取一个极具代表性的"试金石"场景:"用户上传一个大文件(如 CSV/Excel),系统需要在后台异步解析、校验数据,并实时反馈进度"。
这个场景完美覆盖了 Web 开发的几个核心痛点:
- 大文件 I/O:如何处理二进制流而不阻塞内存?
- 耗时计算:解析万行数据,绝不能让 HTTP 请求挂起。
- 异步解耦:如何把"接收"和"处理"分开?
- 状态追踪:用户怎么知道处理完了没?
让我们看看,在 Django (MTV + Celery) 和 FastAPI (DDD + Native Async/BackgroundTasks) 两种架构下,这套流程是如何被拆解和实现的。
🎯 场景定义:核心挑战
假设我们要实现一个功能:
- 输入:用户 POST 一个 50MB 的 Excel 文件。
- 处理 :
- 保存文件。
- 异步读取内容,逐行校验业务规则(耗时操作)。
- 将合法数据写入数据库,记录错误日志。
- 输出 :
- 接口立即返回
task_id。 - 用户可通过轮询或 WebSocket 获取进度(0% -> 100%)和结果。
- 接口立即返回
🐘 方案一:Django 架构 (MTV + Celery)
"重型坦克"模式:稳健、隔离、但略显笨重
在 Django 的世界里,处理耗时任务的标准答案几乎是唯一的:Celery。因为 Django 的核心(ORM, Middleware)主要是同步阻塞的,如果在 View 里直接跑循环解析,整个工作线程就会被卡死,无法响应其他请求。
1. 架构流向
User -> [Django View (MTV)] -> [Redis/RabbitMQ] -> [Celery Worker (独立进程)] -> [DB]
^ |
|______ (Polling API) ______|
2. 核心组件职责
A. Model (数据层)
我们需要一个"任务状态表"来解耦请求和处理。
- TaskLog Model : 存储
task_id,status(PENDING, STARTED, SUCCESS),progress(int),error_message。 - 作用:这是 View 和 Celery 任务之间的"共享内存",用于传递状态。
B. View (控制层)
View 在这里非常"薄",它只做两件事:
- 接收
request.FILES,将文件保存到临时存储(本地或 S3)。 - 调用 Celery 任务:
parse_task.delay(file_path, user_id)。 - 创建
TaskLog记录,立即返回task_id给前端。
- 关键点 :View 绝不触碰解析逻辑。它只是一个"发令枪"。
C. Task (异步层 - Celery)
这是真正的"业务逻辑"所在地,通常位于 tasks.py。
- 独立性 :它是一个完全独立的 Python 进程,不依赖 HTTP 请求上下文(没有
request对象)。 - 流程 :
- 加载文件。
- 循环解析(每处理 100 行,更新一次
TaskLog表的progress字段)。 - 捕获异常,更新状态为 FAILURE。
- 完成后更新状态为 SUCCESS。
- 架构特点 :
- 强隔离:即使解析代码崩溃(Segfault),只会挂掉一个 Worker 进程,主 Web 服务毫发无损。
- 可靠性:Celery 提供重试机制(Retry)、ACK 确认,消息不会丢失。
3. Django 方案的痛点
- 运维复杂:你需要额外部署 Redis/RabbitMQ 作为 Broker,还要单独运行 Celery Worker 进程,甚至需要 Flower 来监控。
- 调试割裂:断点调试时,你必须在 Web 进程和 Worker 进程之间切换,上下文不连贯。
- 状态同步压力 :为了显示进度条,任务需要频繁
save()数据库,高并发下可能造成 DB 写压力(通常需要引入节流逻辑)。
⚡ 方案二:FastAPI 架构 (DDD + Native Async)
"极速跑车"模式:轻量、内聚、极致并发
FastAPI 基于 asyncio,天生具备处理高并发 I/O 的能力。对于文件解析这种 I/O 密集型任务,我们可以利用 BackgroundTasks (轻量级) 或 Arq/Celery (重量级)。在 DDD 架构下,我们倾向于先利用语言特性保持代码内聚。
1. 架构流向 (轻量级原生方案)
User -> [FastAPI Router] -> [Event Loop] -> [BackgroundTasks] -> [Domain Service] -> [DB]
^ |
|______ (WebSocket) ________|
(注:如果是超大规模集群,FastAPI 也可以对接 Celery,但这里展示其原生优势)
2. 核心组件职责 (DDD 分层)
A. Domain Layer (领域层)
- Service :
FileParsingService。- 核心逻辑 :这是一个纯异步函数 (
async def parse(...))。 - 非阻塞 I/O :使用
aiofiles异步读取文件,避免阻塞事件循环。 - 状态回调:Service 不直接操作 DB,而是通过回调或事件总线通知应用层更新状态。
- 核心逻辑 :这是一个纯异步函数 (
B. Infrastructure Layer (基础设施层)
- Repository :
JobRepository(使用 SQLAlchemy Async 或 Tortoise ORM)。- 提供
update_progress(job_id, percent)方法,这是非阻塞的await repo.update(...)。
- 提供
C. Interface Layer (FastAPI Router)
-
Endpoint :
POST /upload。- 接收
UploadFile。 - 关键语法 :利用 FastAPI 的依赖注入注入
background_tasks。
python@app.post("/upload") async def upload_file( file: UploadFile, background_tasks: BackgroundTasks, # 自动注入 current_user: User = Depends(get_current_user) ): job = await repo.create_job(user=current_user) # 将耗时任务加入后台队列,立即返回响应 # 注意:这里传入的是协程函数或普通函数 background_tasks.add_task(domain_service.parse_and_save, job.id, file) return {"job_id": job.id} - 接收
-
实时反馈 :
- 配合 WebSocket (
@app.websocket("/ws/{job_id}"))。 - 在解析过程中,Service 可以通过 WebSocket 管理器直接推送进度给前端,无需前端轮询数据库,体验极佳。
- 配合 WebSocket (
3. FastAPI 方案的痛点
- 内存与进程隔离弱 :
BackgroundTasks运行在主进程的 Event Loop 中。如果解析代码发生严重错误(如 C 扩展库崩溃),可能会拖垮整个 API 服务。 - CPU 密集型瓶颈:虽然 I/O 是异步的,但 Python 的 GIL 限制了解析(CPU 计算)的并行度。如果是纯 CPU 计算(如复杂加密),仍需卸载到多进程或外部 Worker。
- 无内置重试 :原生的
BackgroundTasks没有 Celery 那种"失败自动重试 3 次"的机制,需要自己写try-except逻辑。
⚔️ 深度对比:架构决策的关键点
| 维度 | Django (MTV + Celery) | FastAPI (DDD + Async) |
|---|---|---|
| 并发模型 | 多进程/多线程。依赖 OS 调度,上下文切换开销大,但能利用多核 CPU。 | 单线程协程 (Event Loop)。用户态切换,极高并发效率,但受 GIL 限制 CPU 并行。 |
| 代码风格 | 同步阻塞风格。逻辑被物理分割在 View 和 Task 两个文件中。 | 异步流式风格 。从路由到 Service 全是 async/await,类型提示完整,链路清晰。 |
| 依赖复杂度 | 高。需维护 Broker (Redis), Worker, Monitor。 | 低 。原生支持,仅需 aiofiles 等库,零额外组件。 |
| 状态管理 | 数据库轮询为主。频繁 Update 表,压力大。 | WebSocket 推送 + 内存状态。实时性更好,DB 压力小。 |
| 错误隔离 | 强。Worker 挂了不影响 Web 服务。 | 弱。任务崩溃可能波及主进程(需小心捕获异常)。 |
| 适用规模 | 企业级/超大规模。任务需要持久化、重试、定时、分布式调度。 | 中高并发/实时性要求高。任务生命周期短,追求极致响应速度。 |
| DDD 契合度 | 中。Task 文件往往变成新的"上帝脚本",难以融入领域层。 | 高。异步服务天然可注入领域层,依赖关系清晰。 |
💡 最佳实践建议
1. 什么时候坚持用 Django + Celery?
- 任务执行时间 > 5 分钟。
- 任务需要 精确的重试策略(如:失败后指数退避重试 3 次)。
- 需要 定时任务(如:每天凌晨解析昨天的所有文件)。
- 场景:银行日终结算、大规模报表生成、视频转码。
- 理由 :你需要的是可靠性 和资源隔离,而不是极致的响应速度。
2. 什么时候选择 FastAPI + Native Async?
- 任务执行时间 < 2 分钟。
- 对 实时反馈 有强需求(如:上传即刻看到解析进度条跳动)。
- 并发量极大(每秒数百个上传请求),但单个任务计算量不大(主要是 I/O)。
- 希望架构轻量化,减少运维组件(不想维护 Redis 集群)。
- 场景:用户头像上传并裁剪、小型 CSV 导入、实时日志分析、AI 推理接口。
- 理由 :你需要的是用户体验 和开发效率。
3. 混合模式 (The Best of Both Worlds)
在 2026 年的大型架构中,我们经常看到:
- FastAPI 作为网关和实时交互层,处理文件接收和 WebSocket 推送。
- 对于超重任务,FastAPI 将消息投递到 RabbitMQ ,由后端的 Celery Worker 消费。
- 这样既利用了 FastAPI 的高并发 I/O 优势,又保留了 Celery 的可靠任务调度能力。
📝 总结
- Django 的方案 是工业化的:稳重、可靠、组件化,但略显笨重,适合"稳扎稳打"的大后方处理。它把"异步"当作一个独立的基础设施问题来解决。
- FastAPI 的方案 是敏捷化的:轻快、流畅、语言级支持,适合"唯快不破"的前线交互。它把"异步"当作语言特性融入到业务逻辑的每一行代码中。
在处理"文件上传与解析"这个具体任务时:
- 如果你追求开发速度 和系统稳定性,且任务不急,Django + Celery 是老牌劲旅。
- 如果你追求用户体验 (实时进度)和资源利用率,FastAPI + Async 是现代首选。
04:混合架构实战 ------ FastAPI + Celery 的"黄金搭档"模式
前情提要 :
在上一篇中,我们对比了 Django 的"重型坦克"模式和 FastAPI 的"极速跑车"模式。
但现实世界往往不是非黑即白的。当我们用 FastAPI 构建了高并发的 API 网关,却面临超长耗时任务 (如解析 1GB 的基因测序文件、训练小型 AI 模型)时,原生的
BackgroundTasks显得力不从心(缺乏重试、监控、持久化)。这时候,我们需要引入 Celery 这位"分布式任务处理专家"。
核心问题:FastAPI 是异步的(Async),Celery 传统上是同步的(Sync)。两者结合会不会"水土不服"?代码结构该如何组织才能既保持 FastAPI 的清爽,又拥有 Celery 的稳健?
今天,我们就以 "文件上传与异步解析" 为例,手把手拆解 FastAPI + Celery 的混合架构实现方案。
🏗️ 架构全景图:各司其职
在这个混合模式中,三个组件分工明确:
-
FastAPI (The Receiver) :
- 职责 :接收 HTTP 请求,验证文件,将文件保存到持久化存储(S3/本地),立即向消息队列(Redis/RabbitMQ)发送一个"任务消息",然后秒回响应给前端。
- 特点:极快,不阻塞,利用异步优势处理高并发上传。
-
Message Broker (The Postman) :
- 角色:Redis 或 RabbitMQ。
- 职责:暂存任务消息,确保任务不丢失,负责负载均衡。
-
Celery Worker (The Processor) :
- 职责:监听队列,获取任务,执行耗时的文件解析逻辑,更新数据库状态,发送通知。
- 特点:独立进程,可横向扩展,支持重试、定时、监控。
-
上传文件 2. 保存文件到 S3/磁盘 3. 发送任务 ID 4. 消费任务 5. 读取文件 6. 解析并更新状态 7. 轮询/WS 获取进度 8. 查询 DB 用户
FastAPI App
存储系统
Redis/RabbitMQ
Celery Worker
数据库
💻 核心代码实现原理
我们将采用 DDD 分层思想 来组织代码,避免把逻辑全塞进一个文件。
1. 项目结构建议
text
src/
├── api/ # FastAPI 路由层
│ ├── routes.py
│ └── schemas.py # Pydantic 模型
├── core/ # 配置 (Celery config, DB config)
│ └── celery_app.py # Celery 实例初始化
├── domain/ # 领域逻辑 (纯 Python)
│ └── services.py # 文件解析的核心算法
├── infrastructure/ # 基础设施
│ ├── models.py # SQLAlchemy 模型 (TaskLog)
│ └── storage.py # 文件读写操作
└── tasks.py # Celery 任务定义 (连接层)
2. 第一步:配置 Celery (core/celery_app.py)
这是连接 FastAPI 和 Worker 的桥梁。注意,我们需要在 FastAPI 启动时也能访问到这个 celery_app 实例。
python
from celery import Celery
from src.core.config import settings
# 初始化 Celery
celery_app = Celery(
"worker",
broker=settings.CELERY_BROKER_URL, # 如 redis://localhost:6379/0
backend=settings.CELERY_RESULT_BACKEND # 如 redis://localhost:6379/1
)
# 加载配置
celery_app.conf.update(
task_serializer="json",
accept_content=["json"],
result_serializer="json",
timezone="UTC",
enable_utc=True,
# 关键配置:允许在 FastAPI 主进程中调用 delay 时不需要 event loop 阻塞
task_always_eager=False,
)
3. 第二步:定义 Celery 任务 (tasks.py)
这里是同步代码 的世界。Celery Worker 默认以同步方式运行任务(虽然 Celery 4.0+ 支持 async 任务,但在混合架构中,为了稳定性和库兼容性,通常建议在 Worker 端使用同步代码,或者使用 asyncio.run 包裹异步逻辑)。
python
from src.core.celery_app import celery_app
from src.domain.services import FileParserService
from src.infrastructure.models import TaskLog, get_db_session
from src.infrastructure.storage import read_file_path
@celery_app.task(bind=True, max_retries=3)
def process_file_task(self, file_path: str, task_log_id: int):
"""
Celery 任务:执行耗时的文件解析
bind=True: 允许访问 task 实例本身 (用于 retry 和 update_state)
"""
db = next(get_db_session())
try:
# 1. 更新状态为 STARTED
task_log = db.query(TaskLog).get(task_log_id)
task_log.status = "STARTED"
db.commit()
# 2. 执行核心业务逻辑 (同步阻塞操作,但在独立进程中不影响 FastAPI)
# 假设 parser 是一个同步的重型库 (如 pandas, openpyxl)
parser = FileParserService()
# 模拟进度回调
def on_progress(percent, message):
task_log.progress = percent
task_log.message = message
db.commit()
# 更新 Celery 自带状态 (可选,用于 inspect)
self.update_state(state="PROGRESS", meta={"current": percent})
result = parser.parse(file_path, callback=on_progress)
# 3. 成功处理
task_log.status = "SUCCESS"
task_log.result_data = result
db.commit()
return {"status": "success", "rows_processed": len(result)}
except Exception as exc:
# 4. 异常处理与重试
task_log.status = "FAILURE"
task_log.error_message = str(exc)
db.commit()
# 指数退避重试
raise self.retry(exc=exc, countdown=60)
4. 第三步:FastAPI 路由层 (api/routes.py)
这里是异步代码的世界。FastAPI 只需要负责"点火",然后立即返回。
python
from fastapi import APIRouter, UploadFile, File, BackgroundTasks, HTTPException
from src.core.celery_app import celery_app
from src.tasks import process_file_task
from src.infrastructure.models import TaskLog, create_task_log
from src.infrastructure.storage import save_upload_file
from src.api.schemas import TaskResponse
router = APIRouter()
@router.post("/upload", response_model=TaskResponse)
async def upload_file(file: UploadFile = File(...)):
"""
1. 接收文件
2. 保存文件到磁盘/S3 (异步 IO 优化)
3. 创建数据库记录
4. 触发 Celery 任务
5. 立即返回 task_id
"""
# A. 保存文件 (使用 aiofiles 进行异步写入,不阻塞事件循环)
file_path = await save_upload_file(file)
# B. 创建任务日志记录 (Pending 状态)
# 注意:这里需要异步 ORM (如 SQLAlchemy Async 或 Tortoise)
task_log = await create_task_log(filename=file.filename, path=file_path)
# C. 触发 Celery 任务
# .delay() 是 .apply_async() 的快捷方式,它将消息推送到 Broker,立即返回
# 这是一个非阻塞操作 (取决于 Broker 客户端,Redis 客户端通常是异步友好的)
process_file_task.delay(file_path=file_path, task_log_id=task_log.id)
# D. 立即响应
return {
"task_id": task_log.id,
"status": "pending",
"message": "File uploaded. Processing started in background."
}
@router.get("/task/{task_id}")
async def get_task_status(task_id: int):
"""
前端轮询此接口获取进度
"""
# 查询数据库中的 TaskLog
task_log = await get_task_log_by_id(task_id)
if not task_log:
raise HTTPException(status_code=404, detail="Task not found")
return {
"task_id": task_log.id,
"status": task_log.status,
"progress": task_log.progress,
"result": task_log.result_data if task_log.status == "SUCCESS" else None,
"error": task_log.error_message if task_log.status == "FAILURE" else None
}
⚙️ 关键难点与解决方案
1. 异步 (FastAPI) 与 同步 (Celery) 的摩擦
- 问题 :FastAPI 是
async def,而 Celery 任务通常是def。 - 解决 :
- FastAPI 端 :调用
task.delay()只是发送消息,这个动作非常快,即使是同步的 Redis 客户端也不会造成明显阻塞。如果使用aioredis等异步客户端更佳。 - Celery 端 :保持任务是同步的。因为 Worker 是独立进程,阻塞只会影响当前 Worker,不会卡死 FastAPI 主服务。如果在任务内部必须调用异步库(如
httpx异步版),可以使用asyncio.run()包裹。
- FastAPI 端 :调用
2. 文件传递方式
- 误区 :试图把整个文件内容作为参数传给 Celery 任务。
process_file_task.delay(file_content=...)❌ (消息队列爆炸,性能极差)
- 最佳实践 :只传文件路径 (Path/URL) 。
- FastAPI 先把文件存到共享存储(本地磁盘/NFS/S3)。
- Celery 任务根据路径去读取文件。
- 这样消息队列里只有几十字节的元数据,极其轻量。
3. 状态同步机制
- 方案 A (推荐) :数据库作为单一事实来源 (Single Source of Truth) 。
- Celery 任务直接更新 DB 中的
TaskLog表。 - FastAPI 接口只查 DB。
- 优点:解耦,即使 Celery 挂了,状态也在 DB 里;支持多 Worker 并发更新。
- Celery 任务直接更新 DB 中的
- 方案 B (Celery Backend) :使用 Celery 的
backend(Redis) 存储结果。- 缺点 :不适合存储复杂的进度条信息(需要频繁序列化/反序列化),且查询不如 SQL 灵活。通常只用于获取最终
result或state。
- 缺点 :不适合存储复杂的进度条信息(需要频繁序列化/反序列化),且查询不如 SQL 灵活。通常只用于获取最终
4. 优雅关闭与信号处理
- 当服务器重启时,正在进行的任务怎么办?
- Celery 配置 :设置
task_acks_late=True和worker_prefetch_multiplier=1。- 这意味着:只有任务彻底完成后,Broker 才会确认删除消息。如果 Worker 中途挂掉,消息会重新回到队列,被其他 Worker 拾取重试。
🚀 进阶优化:如何让体验更丝滑?
1. 实时推送 (WebSocket + Celery)
轮询(Polling)太浪费资源了。我们可以结合 WebSocket:
- 架构 :
- Celery 任务完成后(或每更新 10% 进度),向 Redis 发布一个消息 (
redis.publish('task_updates', ...)). - FastAPI 中有一个后台任务监听 Redis 频道。
- 一旦收到消息,FastAPI 通过 WebSocket 主动推送给前端。
- Celery 任务完成后(或每更新 10% 进度),向 Redis 发布一个消息 (
- 效果:前端进度条实时跳动,无需每秒请求一次 HTTP 接口。
2. 动态扩缩容
- 既然是混合架构,Celery Worker 可以独立部署。
- 在 Kubernetes 中,可以根据 Redis 队列长度 (
redis.llen('celery')) 自动增加或减少 Worker Pod 的数量。 - FastAPI 服务专注于接收入口流量,Worker 集群专注于计算,互不干扰。
3. 依赖注入的妙用
在 FastAPI 中,你可以编写一个 Depends(get_celery_task_status),直接在路由参数中获取任务状态,甚至拦截请求(如果任务失败,直接返回错误,不再执行后续逻辑)。
📝 总结:为什么这是"Best of Both Worlds"?
| 特性 | 纯 FastAPI (BackgroundTasks) | 纯 Django (Celery) | FastAPI + Celery (混合) |
|---|---|---|---|
| 响应速度 | 快 | 快 | 极快 (FastAPI 处理 IO 无敌) |
| 任务可靠性 | 低 (进程挂了就没了) | 极高 (重试、持久化) | 极高 (继承 Celery 所有优势) |
| 开发体验 | 简单,但缺乏监控 | 繁琐,配置多 | 平衡 (FastAPI 写接口爽,Celery 写逻辑稳) |
| 资源隔离 | 无 (阻塞主线程) | 有 (独立进程) | 有 (计算压力完全剥离) |
| 适用场景 | 小文件,秒级任务 | 传统企业应用 | 高并发上传 + 重型计算 (AI, 大数据) |
核心心法:
让 FastAPI 做它最擅长的:高并发 I/O、协议解析、实时通信 。
让 Celery 做它最擅长的:分布式调度、可靠执行、耗时计算 。
两者通过 消息队列 和 共享存储 握手,通过 数据库 同步状态。