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 如何真正解耦?三个原则

光有分层还不够,还得遵守一些原则。我曾经在一个项目里踩过坑,分层是分了,但层与层之间耦合得死死的。

  1. 依赖倒置:高层模块不应该依赖低层模块,两者都应该依赖抽象。说白了,Service 层不要直接 new 一个 Model 对象,而是通过接口或依赖注入来获取。
  2. 单向依赖:Controller 依赖 Service,Service 依赖 Model。反过来不行!Model 绝对不能直接调用 Service 或 Controller。
  3. 接口隔离:每一层只暴露必要的方法。比如 Model 层只提供 create_userget_user_by_id 这样的方法,不要把整个数据库连接暴露出去。

我曾经踩过的坑:在 Service 层里直接操作数据库连接对象。后来换了数据库驱动,所有 Service 都得改一遍。正确的做法是:Service 只依赖 Model 层的接口,具体实现由 Model 层自己负责。

3.5 分层架构的「度」

你可能会问:是不是层数越多越好?

不是的。我见过有人把项目分成七八层,结果每层就几行代码,反而增加了理解成本。

我个人习惯:

  • 小项目(几千行代码):Controller + Service + Model 三层就够了
  • 中等项目(几万行代码):可以再加一层 Repository,专门处理数据访问
  • 大型项目(几十万行代码):可以考虑领域驱动设计(DDD),但那是另一个话题了

记住一个原则:分层是为了降低复杂度,而不是增加复杂度。如果分层让你更痛苦,那说明分错了。

3.6 总结

分层架构不是什么高深的技术,它就是一种「分而治之」的思想。MVC 也好,MTV 也罢,核心都是让代码各归其位。

我做了这么多年后端,最大的体会就是:好的架构不是设计出来的,而是重构出来的。不要一开始就想搞个完美的分层,先写出来,再慢慢拆。就像我那个 Flask 项目,一开始也是大泥球,后来才一点点拆开的。

嗯,下一章我们会聊聊「依赖注入」——这是让分层架构真正活起来的关键技术。