Django: 复杂模型与简单控制器?
这是一个关于架构的普遍问题。我在很多地方看到,在MVC框架中,(1) 模型应该“胖”,而控制器应该“瘦”。但我也看到过(2) 具体情况取决于你使用的框架。那么,如果你是在使用django开发呢?
我在使用django的过程中发现,很多逻辑最终都放在了视图和表单里。这里说的不是“业务逻辑”,而是处理请求、会话等的细节。从代码行数来看,这些细节往往比操作模型对象的业务逻辑还要多。我是在做错什么,还是说这就是django的工作方式?
3 个回答
我对Django最大的困扰就是它在MVC模式上加了一个表单层,感觉把这个模式搞坏了。大部分文档都建议你把验证逻辑放在表单里,而模型验证器只有在表单里才会被调用,这种做法更是强化了这种习惯。但我觉得这其实是个坏习惯,因为很多时候我们验证的数据最终都是要转成模型的。
一个很好的例子就是,如果你想把一个传统的Django项目转成一个以API为中心的项目,使用Django Rest Framework,并且有一个单独的前端客户端来调用这个API。这样一来,你就不能简单地保留模型和很多业务逻辑,而是得去修改你的表单,把所有逻辑都搬到序列化器里(不幸的是,Django Rest Framework也遵循了Django的这种不太好的MVC模式,还多加了一个“序列化器”层)。
我觉得应该采用“胖模型”的方法。关于如何在Django中实现这一点的更多信息,可以点击这里查看。
这要看你的应用程序是做什么的,但Django的一个优点就是它不强制你把逻辑代码放在视图或模型里,这完全由你决定。
如果你觉得某些逻辑和你的模型关系很紧密,那你可以把它做成一个方法。我的原则是,模型应该和环境无关(比如说,网页应用、定时任务等)。
我的做法是尽量在模型里放最少的东西。
顺便问一下,你不会打算在模型里处理request
和sessions
吧?那可不是个好主意。
MVC(模型-视图-控制器)并不是一个万能的解决方案,很多时候它被用得不对,无法兑现它的承诺:实际上,修改一个模型时,控制器也需要做相应的修改,因为用法不当。如果你真的想要模型和控制器之间的松散耦合,那么——而且很多人通常忽略这一点——你必须使用一种叫做服务模式(打开为图片)。但几乎没有人真正这样做。
与其在PHP的世界里盲目遵循MVC的繁琐规则,Django采取了一种务实的做法。因为在软件开发的现实中,开发者是为用户展示东西的。然后用户(你的老板、客户、顾客……)会“看到”你的工作,最终给出他想要的“看法”。使用Django,开发者可以采用一种更“以视图为中心”的开发模式,结果是:这让遵守截止日期变得更容易,用户也更满意。如果你仔细想想,它有一种“nosql式”的理念,即视图(一般的视图,不是Django的视图)应该主导后台发生的事情。
我想感谢Django,因为它没有像99%的PHP MVC实现那样搞错MVC。
另一方面,Django是唯一一个允许应用程序之间适当隔离的框架。每个应用程序可以拥有:
- 模型
- 视图
- 模板
- 网址
- 静态文件
- 测试
- 表单
- 可选的附加功能(管理员、ajax选择的过滤器、django-authority的权限、django-notifications的通知等等)
所以即使你的模型、视图和模板是紧密相连的,你的项目也可以相对分成小的(也就是容易维护的)和松散耦合的应用程序。只有相关的模型、视图、模板等才会被联系在一起。一个庞大的模型脚本和一个庞大的视图及网址脚本并不是你在Django中想要的。例如,你不希望两个模型类像Article
和FootballMatch
放在一起,你希望创建一个“文章”/“博客”应用和一个“体育”应用,它们可以独立存在。当然,有时候它们必须联系在一起,在这种情况下,在90%的情况下可以在项目级别做到(如果需要将模型或模板标签联系在一起,你可以创建另一个应用“blog_sport”)。
例如,在模型类中定义一个get_absolute_url()
方法是一个非常常见的做法。是的,你的模型类理论上应该只包含业务逻辑,但现在它和你的网址定义紧密相连。这在实践中有多糟糕呢?实际上,这非常聪明,因为添加这个方法只需要两秒钟,然后你可以在任何使用模型的地方使用它:无论是在视图还是模板中。此外,其他应用(例如django.contrib.admin
)也会使用它。
另一个稍微复杂一点的Django的聪明之处在于查询是懒惰求值的。这意味着,你的视图函数/类会定义一个查询,比如blog_list = Blog.objects.all()
,但这个查询实际上会在模板中执行,如果它像{% for blog in blog_list %}
那样调用。所以在这种情况下,业务逻辑发生在模板中,如果在渲染模板之前出现问题,你就节省了一次查询。但这还不是全部,如果你的模板只是显示一个计数{{ blog_list.count }}
,那么select查询根本不会被执行,而只会执行一个计数查询。“一般视图”决定了需要什么业务逻辑。这远离了MVC的承诺,但说实话:这有多实用呢?
我的观点是,你可以错误地应用理论,正确地做(这会把你的选择减少到大约5个web框架,包括所有语言),或者以优雅和务实的方式直接达到目的,快速完成工作:这就是Django的选择。