3、分层架构:MVC、MTV模式在Python项目中的实践,如何解耦业务逻辑
3.1 为什么我们需要分层?
说实话,我刚开始写Python后端的时候,代码那叫一个「豪放」。
路由里直接写数据库查询,视图函数里塞满业务逻辑,模板里还偷偷调接口。一个文件上千行,改个需求得翻半天。后来项目越来越大,这种「大泥球」架构让我吃尽了苦头。
分层架构,说白了就是「各司其职」。把不同职责的代码拆到不同层,让每一层只关心自己的事。你想想看,这样改起来是不是轻松多了?
核心思想:关注点分离(Separation of Concerns)。每一层只做一件事,并且把它做好。
3.2 MVC模式:经典的「三兄弟」
MVC 是 Model-View-Controller 的缩写。我在2015年第一次接触 Django 时,就被这种模式吸引了。
- Model(模型):负责数据。说白了就是跟数据库打交道,定义数据结构、做增删改查。
- View(视图):负责展示。用户看到什么,按钮长什么样,数据怎么排列,都是 View 的事。
- Controller(控制器):负责调度。用户点了个按钮,Controller 去调 Model 拿数据,再交给 View 渲染。
嗯,这里要注意:在 Python 的 Django 框架里,名字有点不一样。Django 自称是 MTV 模式,但本质是一样的。
| MVC | Django MTV | 职责 |
|---|---|---|
| Model | Model | 数据层,定义表结构、ORM操作 |
| View | Template | 展示层,HTML模板、前端渲染 |
| Controller | View | 逻辑层,接收请求、调用Model、返回响应 |
你看,Django 的 View 其实承担了 Controller 的职责。名字虽然换了,但分层的思想没变。
3.3 一个真实的解耦案例
我记得有一次接手一个 Flask 项目,代码全写在 app.py 里。用户注册的逻辑是这样的:
# 糟糕的写法:所有逻辑混在一起
@app.route('/register', methods=['POST'])
def register():
username = request.form['username']
password = request.form['password']
# 校验逻辑
if len(username) < 3:
return '用户名太短', 400
if len(password) < 6:
return '密码太短', 400
# 数据库操作
conn = sqlite3.connect('db.sqlite')
cursor = conn.cursor()
cursor.execute('INSERT INTO users (username, password) VALUES (?, ?)',
(username, password))
conn.commit()
# 发送邮件
send_email(username, '欢迎注册')
return '注册成功'
这段代码有什么问题?问题大了去了!
- 校验逻辑、数据库操作、邮件发送全揉在一起
- 没法单元测试——你总不能每次测试都真的发邮件吧?
- 改需求时牵一发动全身——比如要加个「用户名不能重复」的校验,得改整个函数
我当时花了三天时间重构,把它拆成了三层:
# 1. Model层:只关心数据
class UserModel:
def create_user(self, username, password):
conn = sqlite3.connect('db.sqlite')
cursor = conn.cursor()
cursor.execute('INSERT INTO users (username, password) VALUES (?, ?)',
(username, password))
conn.commit()
return cursor.lastrowid
# 2. Service层:业务逻辑
class UserService:
def __init__(self):
self.user_model = UserModel()
self.email_service = EmailService()
def register(self, username, password):
# 校验逻辑
if len(username) < 3:
raise ValueError('用户名太短')
if len(password) < 6:
raise ValueError('密码太短')
# 调用Model层
user_id = self.user_model.create_user(username, password)
# 调用其他服务
self.email_service.send_welcome(username)
return user_id
# 3. Controller层:只做调度
@app.route('/register', methods=['POST'])
def register():
try:
user_service = UserService()
user_id = user_service.register(
request.form['username'],
request.form['password']
)
return f'注册成功,用户ID: {user_id}'
except ValueError as e:
return str(e), 400
我的习惯:Service 层是解耦的关键。所有业务逻辑都放在 Service 里,Controller 只负责「接请求、调 Service、返回结果」。这样测试时只需要 mock Service 层,不用启动 Web 服务器。
3.4 如何真正解耦?三个原则
光有分层还不够,还得遵守一些原则。我曾经在一个项目里踩过坑,分层是分了,但层与层之间耦合得死死的。
- 依赖倒置:高层模块不应该依赖低层模块,两者都应该依赖抽象。说白了,Service 层不要直接 new 一个 Model 对象,而是通过接口或依赖注入来获取。
- 单向依赖:Controller 依赖 Service,Service 依赖 Model。反过来不行!Model 绝对不能直接调用 Service 或 Controller。
- 接口隔离:每一层只暴露必要的方法。比如 Model 层只提供
create_user、get_user_by_id这样的方法,不要把整个数据库连接暴露出去。
我曾经踩过的坑:在 Service 层里直接操作数据库连接对象。后来换了数据库驱动,所有 Service 都得改一遍。正确的做法是:Service 只依赖 Model 层的接口,具体实现由 Model 层自己负责。
3.5 分层架构的「度」
你可能会问:是不是层数越多越好?
不是的。我见过有人把项目分成七八层,结果每层就几行代码,反而增加了理解成本。
我个人习惯:
- 小项目(几千行代码):Controller + Service + Model 三层就够了
- 中等项目(几万行代码):可以再加一层 Repository,专门处理数据访问
- 大型项目(几十万行代码):可以考虑领域驱动设计(DDD),但那是另一个话题了
记住一个原则:分层是为了降低复杂度,而不是增加复杂度。如果分层让你更痛苦,那说明分错了。
3.6 总结
分层架构不是什么高深的技术,它就是一种「分而治之」的思想。MVC 也好,MTV 也罢,核心都是让代码各归其位。
我做了这么多年后端,最大的体会就是:好的架构不是设计出来的,而是重构出来的。不要一开始就想搞个完美的分层,先写出来,再慢慢拆。就像我那个 Flask 项目,一开始也是大泥球,后来才一点点拆开的。
嗯,下一章我们会聊聊「依赖注入」——这是让分层架构真正活起来的关键技术。