严格来说,MVC是一个古老的概念,从早期的Smalltalk开始已经不再适合于web应用开发。Django的开发人员已经正确意识到在Python中我们实际使用的是MTV(model,template,view):
模板包含了HTML的内容以及页面显示逻辑。它是使用模板语言如Kajiki来编写,从视图(view)中获取数据,然后展示在页面中。
视图(有时也称为“控制器”),仅仅是使用Python语言编写的中间代码。它借助于web框架将所有的内容放在一起。它可以看到其他的所有层,并且定义了URLs,将它们映射到web框架中用于接收数据的函数,然后利用其他层以最终发送响应给web框架。它应该尽可能得小,因为它的代码是不能重复利用的,即使你尽量地缩减它,web表单也会促使其逐渐地变得复杂。
模型层本质上是一个持久层:它最重要的依赖就是SQLAlchemy。模型知道如何去保存数据,构成整个项目中最可重用的代码。当然它并不清楚HTTP相关的内容和你所使用的框架。它代表了排除用户界面细节的系统本质内容。
但是稍等下,哪里?在视图还是模型中?你应该在哪里放置程序的灵魂:业务规则?模板层已经自动被排除掉,因为它并不是Python编写的。所以剩下3个可能的答案:
视图层,这是最糟糕的选择。视图层应该仅仅包含中间代码,将代码数量保持尽可能得小,并且同系统中的其他部分隔离开,所以系统应该能在web框架中、使用中以及单元测试中独立访问。另外,业务逻辑应该存在于更加可重用的地方。视图层被视为展示逻辑的一部分,所以业务逻辑被排除在外。实际上,除了Web UI之外,在创建desktop UI时,开发者应该忽略视图和HTTP相关的内容,需要业务逻辑尽可能地被重用,因此,我们应该排除掉视图层。
模型层,这个是可能的选择,因为模型层至少是可重用的。但是模型层主要关注于持久化,它应该更少地依赖于SQLAlchemy(它已经是一个非常复杂的东西)。
新的层,这才是正确的答案。下面将举例来更好地理解这部分内容。
如果你要创建一个博客,那么MTV正好满足你的需求。但是对于更加复杂的项目来说,其实还是至少缺了一层。你应该将业务逻辑旋转到一个新的、可重用的层次中,大多数人称之为“Service”层,但是我更喜欢称之为”Action“层。
为什么你需要这层?
在大型应用中,单个用户的操作引起多项活动是非常常见的。比如,用户成功地注册了你的服务,那么你的业务逻辑中可能会触发非常多的后台处理:
在关系数据库多张表中新增数据,使用了模型层。
将发送邮件给用户的任务放置到队列中。
将发送短信给用户的任务放置到队列中。
将创建实际使用服务时必需的空间和其他准备性资源的任务放置到队列中。
将更新用户数据的任务放置到队列中。
….
这是一个理解“业务逻辑“的好例子:给定一个用户操作(比如注册),系统就要做一些必需的操作。这种业务逻辑被单个函数捕获会更好;这个函数应该在哪一层呢?
如果所有的这些都实现在模型层,你能想象到它将变得多复杂吗?模型层在只面对持久化时已经很艰难了。现在想象一下模型层处理所有这些事务,它要使用多少外部服务?文件头部应该包括多少imports?反过来看,有多少模块愿意引入这个模型,可能在系统启动前就因为创建的循环依赖而导致系统崩溃。
循环依赖其实就是你没有正确认清系统架构的明显的标识。
对于依赖于Celery的模型来说,了解如何去发送邮件、短信以及使用外部服务等是不应该的做法。持久化对于模型层来说已经是非常复杂的主题了。你应该在模型层之外去处理这些业务逻辑——在模型层和视图层之间的一层。所以称之为”Action“层。
另外,模型层在关系型数据库中经常被映射到一张单独的表。如果你在用户表和订阅表中插入一条记录,哪一个模型应该包含上述逻辑呢?这几乎是不能确定的。因为实际执行的操作远超过了用户表和订阅表的范围。因此,业务逻辑应该被定义在任何模型之外。
当开发人员执行维护时,有时她想按步骤地执行每一步,而有时她想一次性执行完所有操作。分别地实现这些操作并在单个Action层函数中调用是有帮助的。
你可能会怀疑我提出的方法难道不是反面模式域模型的一种吗?没有动作的模型恰恰与面向对象设计相反!我并没有说“将所有的方法从模型中移除”。我的意思是指“将需要外部调用的方法移除”。模型中的方法仅仅用来使用它所需要的数据,并且属于模型中的那些数据。一个面向世界的,调用外部服务的并且很少使用自身数据的方法不应该被放置在模型中。
另一个使得这种架构成功的原因就是测试。TDD教会了程序员去让程序变得解耦,这样做通常会使得软件更加健壮。如果你想要创建一个Celery的应用,并且在你测试之前已经知道了其他的外部服务,那么你将经常陷入头痛中。
还有将业务逻辑放置在视图层之外的最后一个原因。在未来,当你最终决定从Flask过渡到Pyramid时,你很乐意将视图层保持简洁。如果所有的视图都是在与web框架间交互,并且动作层会执行所有的函数,那么你的代码就做到了非常好的隔离。Web框架通常都很贪婪,不要让你的系统跟随他们的脚步。
所以下面就是我所提议的在Python中构建大型应用的层次结构:
模型层是最底层的,最可重用并且可见的层。它仅专注于持久化,模型层是可以包含动作的,只不过这个动作仅仅属于这个模型。模型可以被其他层所返回,以各种方式在请求的结尾返回给模板。
外部服务。对每个服务都创建一个比如说发送邮件。
动作层。这是系统中的核心层,它包含了业务逻辑以及工作流。它使用外部服务去实现特定的目标并且借助模型层来持久化数据。通过以上这些层,它支撑起了整个系统,包括配置,除了用户界面。
模板层仅包括了页面展示逻辑比如说从列表中循环输出构成一个HTML表格。
视图层,这是最高层的,最不可重用的层。它依赖于(与系统中其他层隔离)web框架。并且依赖于表单验证库。它可以看到模板层以及动作层,但是不能直接调用模型层——它必须通过动作层。但是当一个动作层返回了模型数据,那么它可以被传递给模板中(一个Celery任务可以类比于一个web视图)。
这种体系结构有助于避免了在会话层中进行调试因为它清楚地定义了各自的职责。同时它也是明显经得起检测的,因为它做到了很好的解耦,因此可以减少测试的并且减少了模拟的次数。
好的架构总是解耦性非常好的。如果你曾经陷入到一个循环依赖中,你可以想一下是否真的定义好了每一层的职责。当你放弃了并且从一个函数中引入内容,那么你的架构已经失败了。
这并不是说你的web应用必须与Celery应用隔离开。在他们之间可以重用代码——特别是模型——但是对于Celery应用来说,不应该引入web框架!获取配置也不例外,因为在Python中读取配置是非常简单的。