也許後端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分離成兩層,一層負責綁定路由,另外一層負責業務邏輯。

相關文章
相關標籤/搜索