也许后端MVC的说法已通过时了

呃,标题有点耸人听闻,不过我并非标题党。考虑到谈论大而虚的东西(好比最好的语言)容易引发争论,因此还请诸君带着看戏而不是庭辩的心态来看待本文。html

依我我的所见,后端框架,相似于MVC这样的组织方式已经显得过气了。前端

过去,在建立应用时一般会按MVC各建一个文件夹,每一个文件夹就是一个模块。MVC三者的职责是这样的:程序员

  1. Controller绑定到某个路由上,接着处理请求参数,而后建立在整个请求中可见的对象,并进行一些业务逻辑上的工做。期间会从数据库中构造Model,也有可能新建/修改Model后将它们保存入数据库。最后,Controller会经过View响应用户的请求,或者返回重定向的报文。基本上业务逻辑都是实如今Controller里面的。(下文为了阐述方便,都以“业务逻辑”特指Controller中的业务逻辑)数据库

  2. 每一个Model每每对应数据库的一个实体。Model类除了装数据以外,还提供了跟本身身份相关的一些方法,好比User类提供authenticate方法以用于验证密码。Model对象一般还会负责检验构造本身的参数是否正确。后端

  3. View负责使用给定的参数渲染模板,并做为响应返回给用户。View通常是由该框架提供的模板语言写成。设计模式

如今,一个后端应用若是仍是按MVC的方式划分,彷佛有些不适应了。服务器

后端MVC的历史

咱们先从后端MVC的历史开始讲起吧。MVC最初发端于客户端程序的开发,旨在用Controller在Model和View之间进行解耦。若是我没记错的话,gang of four的《设计模式》里面提到MVC,就是以客户端程序做为例子。session

跟客户端开发相似的,后端程序一开始也只有View这一层。在好久好久之前,后端程序都是这样运行的:用户一个HTTP请求进来,服务器经过cgi调用一个程序生成文本做为响应。这时期大部分后端程序,看上去都像是模板语言(见过初学者写过的JSP/PHP吗?)。由于它们主要作的事情,就是从用户输入和数据库中获取数据,并拼接字符串生成文本。后来后端程序开始演化得愈来愈复杂,单单一层View已经不适应了。因为须要把逻辑从View中分割开来,后端程序开始走上客户端程序走过的路,进行MVC的分离。因而,负责路由和业务逻辑处理的部分变成了Controller,负责数据处理和持久化的部分变成了Model。框架

虽而后端程序也是作了MVC的拆分,可是它跟客户端的MVC实际上是不一样的。
在客户端里,Controller把View和Model分离开来,实现View和Model的解耦合。View上的变化,经过Controller传递给Model,而后再将Model最新的数据经过Controller传递回View。View:Controller:Model的比例一般是N:1:N,其中每一个View基本对应一个Model。函数

然而,在后端程序里面,View和Model一般没有很强的对应关系。通常意义上的CRUD,基本上是Controller(业务逻辑)围绕着Model(数据层)在转。View扮演的每每是跑龙套的角色。

还须要V层吗

在客户端MVC中,View扮演的是跟其余两者三足鼎立的角色。用户的输入通过View,底层数据的变动经过View反馈给用户。

然然后端MVC中,View的地位风雨飘摇、无关紧要。前文提到,Controller绑定在路由上,接收请求;Controller渲染模板,发送响应。跟客户端不一样的是,Controller只有在渲染模板时才用上View。View的戏份一会儿被砍掉了一半。祸不单行,Controller并不必定须要渲染模板来发送响应,它可能直接就重定向了;或者更常见的是,Controller直接把一个对象JSON化,并把它响应给用户。这么一来,View的戏份还剩多少?

有些后端框架提供了JSON格式的模板,多多少少试图挽救View的没落地位。惋惜并无什么用。
过去,View一般由三部分组成:html代码,控制流程语句,渲染时的上下文。若是提供的是JSON格式的模板,那么View的前两部分基本不须要了,只须要渲染时的上下文。但是这么一来,为何我还须要渲染一个JSON模板,直接用上下文的数据建立出个实例,由它生成JSON字符串,不也行吗?虽然我多了个用于响应的类,可是少了个JSON模板啊,并且说不定就不用给View留个文件夹。

最近我参与开发的几个后端应用,根本没有View的容身之地。全部的响应都是JSON格式,都是由特定的类JSON化出来的。
不少状况下,后端应用要么仅仅是大后端系统中的一个组件,要么须要跟多种来源的客户端打交道。一般它们只是做为数据的守护者,API的执行人,仅响应以JSON数据。至于接收者想用这些数据作什么,那是它们的事了。也许是拿来渲染前端页面,也许是保存在客户端的数据库里,也许是拿去进一步分析处理。View已经退化到算不上一个层了。

M负责数据实体仍是负责数据的访问

说完风雨飘摇的View,接着说地位尴尬的Model。Model是数据的化身,后端开发千变万变,核心都是数据的处理。能够说,Model就是占了个风水宝位。不过在我看来,当前常见的作法——只划分一个Model包——并不够清晰。

以我愚见,后端程序中的Model其实作了两件事。一件事是表示了数据实体,另外一件则是负责数据的访问。按照单一职责原则,Model这样一身饰两角是不对的。数据实体是一回事,对应的数据实体的访问是另外一件事,二者不能混起来。

假设保存Account须要一个事务,在这个事务里面要更新AccountBalance两个实体。下面是Rails里面的作法:

# always save Account in a transaction
Account.transaction do
  balance.save!
  account.save!
end

问题是,这段代码应该放到哪里?一个作法是放到Controller里面,可是保存Account的方式,不该该放到Account里面吗?另外一个作法是放到Account类里面,可是为何不放到Balance里面呢,这个事务也保存了Balance。做为程序员,在这件事上可不能偏爱哦。
若是提供了DAO做为中间层,那么就不会这种“偏爱”的顾忌了。并且这种带事务的保存,跟Account类自带的save方法的差别,也从层级上体现出来。

此外,Model层里面的类,不必定对应着数据库上的表。每一个Model都知道如何持久化自身数据,这种假定是没法一直保持下去的。若是没有把数据实体和访问数据实体的组件区分开来,总有一天会陷入名不符实的危机中。

一个好的例子是,SQLAlchemy提供了Session类来完成对具体数据(Model)的访问操做(事务、保存等等),这样仅需稍加包装,咱们就能分离出一个数据访问层出来,避免数据实体和数据访问间纠缠不清。

session = Session()
try:
    account = session.query(Account).get(...)
    balance = session.query(Balance).get(...)
    ... # 对account和balance作些修改
    session.commit()
except:
    session.rollback()

C:什么都往里装

调侃了View和Model,是时候对最后的Controller下手了。相对于View负责展现,Model负责数据,Controller的职责并不清晰。Controller是个筐,什么均可以往里面装。凡是没法区分到View和Model的,都放到Controller里面吧。因此,在MVC中,Controller每每是最臃肿的。

终于有一天,咱们下定决心要整治下Controller乱七八糟的环境。一个一般的作法是,把某个路由上Controller的函数,拆分红若干个小函数。这些小函数不绑定路由,纯粹就是业务逻辑的抽象。拆分以后,Controller再也不臃肿了,抽象出来的业务逻辑也能够被复用。
其实往更深一点思考,也许Controller原本就能够拆成两部分,一部分负责绑定路由,另外一部分负责业务逻辑。

  • 绑定路由的部分,负责解决请求数据的完整性和正确性,及限流、鉴权等操做。在它的眼里,看到的是HTTP报文。

  • 业务逻辑的部分,负责具体业务处理。在它的眼里,看到的是用户的操做。

这样一来,业务逻辑的实现就跟路由绑定解耦合。咱们能够给不一样的路由提供同样的业务逻辑处理的同时,保持在限流等方面上的区别对待。咱们也能够以此解决API设计上的遗留问题——旧的API,就让它们调用到新的业务逻辑上。

新的划分方式

  • View消亡了

  • Model分离成两层,一层负责数据实体,另外一层负责数据的访问。

  • Controller分离成两层,一层负责绑定路由,另外一层负责业务逻辑。

相关文章
相关标签/搜索