在寫乾貨以前,我想先探(qiang)討(diao)兩個問題,模式的侷限性?模式有什麼用?javascript
最近看到一篇文章對我啓發很大,許來西在知乎的回答《哲學和科學有什麼關聯?》,全篇較長,這裏摘錄我要引出的一點:前端
科學做爲一種經驗主義的認識論,有着經驗主義的巨大缺陷:它永遠不能產生絕對正確的真理。這是概括法的本質決定的。並且值得注意的是,概括不具備惟一性。java
舉一個簡單的例子,咱們假設一個世界,以下圖:web
科學家很快有了兩種概括方式:算法
世界上全部的青蛙都戴眼鏡express
世界上全部戴眼鏡的都是青蛙編程
在沒有更多的信息的時候,咱們應該如何選擇正確的理論呢?答案是沒法選擇。c#
舉個模式的例子,Scott Wlaschin 在《Functional Programming Design Patterns》(函數型編程模式)中對比了經常使用面向對象模式、原則,在函數型編程語言裏面等價實現:後端
OOP 和 FP,到底哪一種編程範式更加先進呢?答案一樣是沒法選擇。只能在不一樣的時候選用不一樣的假設和不一樣的理論來解釋問題,許來西的文章講到科學必定程度上經過放棄一向性換取了實用性,放棄自洽性換取了它洽性。科學追求實用和工具(實用主義和工具主義)。當我看完許來西的文章,欣喜若狂,一直對編程技術理論的善變和不自洽感到恐懼和厭惡,其實只是經驗主義科學發展的必然過程,善變表明更好的理論(更方便)在替換基礎理論,表明蓬勃發展。設計模式
因此我想引入第一個觀點:
模式是一套立足於特定背景,基於共性總結出的方案,它毫不是真理。
瞭解這些有助於幫助從對模式的盲目崇拜到探究它的實用性和工具性,也就是我要引出的第二個問題:模式有什麼用?
很差好寫代碼看哲學文章不是偶然,在文章落筆以前,我有思考過在 JavaScript 這門動態,多範式,單線程,基於事件I/O的語言環境下,甚至在當前時代,模式是否還有意義?顯然我不是惟一這樣想的,還有篇深度好文《20年前GoF提出的設計模式,對這個時代是否還有指導意義?》。這篇文章引經據典,摘錄了GoF(又稱Gang of Four,即Erich Gamma, Richard Helm, Ralph Johnson & John Vlissides)在設計模式一書中觀點:
這本書的實際價值也許還值得商榷。畢竟它並無提出任何史無前例的算法或者編程技術。它也沒能給出任何嚴格的系統設計方法或者新的設計開發理論——它只是對現有設計成果的一種審視。你們固然能夠將其視爲一套不錯的教程,但它顯然沒法爲經驗豐富的面向對象設計人員帶來多少幫助。
換言之,模式顯然毫無實際用處。
不只如此,文章還列舉了一度模式濫用致使許多弊端,可謂警鐘長鳴。
可是……模式這一稱謂仍然不斷出現,直到今天咱們亦在大量使用。爲何?GoF實際早設計模式的書中作出了預言:
「設計模式爲設計師們提供一種共通的詞彙儲備,幫助其溝通、編寫文檔並探索設計方案。設計模式容許咱們立足於高級抽象層面進行探討,而非設計標註或者編程語言,這就大大下降了系統複雜性。設計模式提高了咱們設計及與同事進行設計探討時的切入點層級。」(第389頁)
簡言之,模式方便了咱們的溝通,提高了思考問題的抽象層級。
這個意義很是巨大,想象一下沒有 MVC 架構模式,可能全部的 Web 框架必然的會實現一套幾乎解決一樣問題的方案,可是命名和文檔卻各不同,當你去看一個新的框架文檔的api 接口,從頭至尾看完之後才恍然大悟,這不就是以前用的框架裏面的 XXX 相似嗎,這樣的編程世界簡直地獄。慶幸的是,得益於計算機科學家(碼農)對問題和方案持續的抽象成模式,使得當前高度複雜的計算機科學也能獲得合理分層和適配,大大簡化了學習和溝通的成本。
爲了感謝模式,是時候學習一波了,本文要介紹的主要有三種架構模式:Middleware,MVC,DI。
Middleware 中間件模式
相信作過 Node.js 服務端開發的同窗對這個模式必定不陌生,考慮以下 Web 應用的場景:
在一個簡單的 HTTP 請求響應週期裏,有以下條件處理,
記錄開始時間
須要驗證用戶的身份 authentication。
解析cookie 並加載body
根據路由返回不一樣的業務處理結果
沒有命中路由則返回404頁面
記錄日誌
記錄總共花費時間
處理異常並顯示頁面(開發環境)
有些處理會根據是否成功決定是否繼續後面的粗粒,有些處理會生成額外的數據,還有的要求攔截某些處理的開始和結束,最後異常處理和記錄日誌要求必定被執行。
通常的解決方法是用嵌套條件判斷結合 try catch finally return 等控制語句,可是這樣的方案會致使代碼碎片化和複製粘貼的編碼風格,由於控制流和邏輯耦合到了一塊兒。理想的方案應當以下:
中心化控制流
解耦處理模塊(重用性)
聲明式、可配置的服務(配置和代碼無關)
這些場景由來已久,好久之前J2EE總結了 Intercepting Filter 模式,有興趣你們能夠看看這篇文(lun)章(wen),裏面由淺入深提到三種方案,其中最初級的方案代碼以下:
這個和 express 和 Koa 的中間件模式極其類似,可是由於靜態語言自己一些特徵,致使最後造成的企業級代碼極其繁瑣,而且有許多侷限性。最主要的問題是處理模塊之間難以重用和共享數據,由於 ServletRequest ServletResponse 沒法動態添加屬性。以致於 JavaEE 把這個模式的適用性加了許多限制,包括和核心處理邏輯分開。
在動態語言的世界裏面,咱們能夠很方便的往 req 和 res 裏面添加數據(基於約定),由於沒有了不少 OOP 世界裏面的」束縛「,Node.js 的實現一般更加優雅和通用。
Express 中間件模式
express 實現現在普遍接受的 Middleware 中間件模式。中間件的意思是在 請求 和 響應 中間執行的函數(爲了區分另外一箇中間件),簽名以下:
這個模式包含了一套聲明式的路由規則,和 middleware 函數上的 next 簽名,它們共同構成了整個中間件模式的控制流,如圖:
這個模式的核心構成不是權限,解析等中間件邏輯,而是路由判斷,next和中斷響應(驗證失敗、解析失敗),其做爲中間件執行控制,解耦了具體的處理邏輯,使得更容易寫出通用的細粒度的中間件。express 內置的強大的聲明式路由,而且路由和 middleware 分離能夠說是它最成功的設計之一。
然而在一些稍微複雜點的業務中,好比一個網站有管理端和用戶端,兩個端至關於獨立的app。express 4.0 提供了一個很是強大的功能 Router。Router 拓展了鏈式決策變成樹形決策,可讓 express 更好的支持大型項目。
Koa 異步中間件模型
Koa 的異步中間件模式-洋蔥模型,相比 Express,其中間件函數返回 Promise,支持 async/await,而且能夠輕鬆實現前置和後置的處理。毫無疑問這個模式更加先進,一些在 express 裏面很差實現的攔截處理邏輯,好比異常處理和統計時間,在 Koa 裏用一箇中間件就能搞定。然而遺憾的是 Koa 自己只提供了 Http 模塊和洋蔥模型的最小封裝。
將來我看好 Koa,其實 express 也意識到這點,他們計劃在 5.0 版本里添加 Promise 的支持,然而做爲一個老牌和完整生態的框架,要克服的困難遠不是技術層面上看似的簡單,直到目前仍然沒有看到 5.x 宣佈支持 Promise, 讓咱們拭目以待。
MVC 模式
MVC 模式也須要介紹嗎,咱們每天都在聊 MVC,無論前、後端框架,說一句 MVC,對一下眼神,基本肯定對方懂你了。
事實是,前端框架已經不適合用 MVC 討論,這個模式從1979年提出以來,做爲萬精油模式,在各個框架和場景中被套用,揹負了太多的歷史包袱,你們能夠看 winter 的文章 談談UI架構設計的演化。撥亂反正我以爲有但願,討論前端框架你們之後統稱 MV 模式就行了,就是模型和視圖分離。
咱們今天要講的 MVC 模式是指在服務器上(後端) MVC 模式,它的定義經受了時間和實踐的檢驗,在許多企業級 Web 框架的實現中高度一致。先列舉場景:
若是你的網站只有幾個簡單的頁面,全部邏輯都寫在 Controller 裏面,是沒有問題的。隨後網站迅速的增加,你發現,
許多頁面裏面的視圖是一致的,可是背後的數據模型不一致。好比:網站上幾乎沒有一個視圖或者組件是獨一無二的,表格,下拉框等。
許多頁面裏面的數據模型是同樣的,可是展示的視圖不一致。好比:同時支持PC和移動端,國際化本地化。
咱們作一個數學模型模擬極端狀況,你們很容易能看到問題
假設左邊是咱們的系統最終的樣子,它恰好能夠表示成 M(模型)和 V(視圖)的內積,咱們更傾向於右邊的表達,由於它更簡潔並且沒有重複。這裏的內積操做你們就能夠理解成控制器,實際上不會如此巧合,可是分離模型和視圖幫助咱們提升代碼複用,下降設計複雜度的好處是很顯然的,一個更通用的表達
模型視圖和控制器之間都是單向連接,因此整個系統的行爲很是可控且容易測試,單獨把路由分開是想強調 Router 和 Controller 是兩個概念,Router 只是一個觸發器(或者提供了一種映射關係),在寫測試的時候,咱們也能夠跳開 Router 單獨調用 Controller。
看到上面的兩種模式,是否是已經開始想,那有沒有一個框架同時是 Koa + Router + MVC 呢,推薦你們一個很是好用的企業級 Web 框架 ThinkJS 3.0,最新版的 ThinkJS 集成了大量最佳實踐和完善的文檔,無論是學習或者企業級開發都很是推薦。並且 ThinkJS 一樣實現了接下來要講的模式。
DI 依賴注入模式
仍是先說場景,假如服務端須要實現session,前期考慮到成本和用戶量,單臺服務器存到文件就夠用了。後期若是用戶量大的時候,須要橫向擴展(Scale-out),就把 session 實現基於中心化的 Redis 服務。
咱們系統設計目標是:
不須要修改業務邏輯代碼實現替換
不須要關注服務的建立和生命週期
解決這類系統擴展性問題有一個很是著名的設計原則 控制反轉(IoC Inversion of control),而 依賴注入(DI dependency injection) 就是其中的一個實現模式。
DI 的基本思路是這樣,首先咱們的代碼不能依賴具體的服務,須要總結概括出一套抽象接口,業務實現依賴接口,而服務實現接口,最後經過框架專門負責建立和提供接口的實例。
這裏的 IoC 容器或者說 Ioc 框架,會在啓動的時候讀取配置文件,並在運行的時候根據須要建立實例提供給使用者,在靜態語言如 java,c# 須要用到反射等高級語法,而 JavaScript 自己是動態的,接口基於約定,而且使用的方式也更加靈活。好比 ThinkJS 3.0 裏面的 extend 和adapter 就能夠理解成接口和實現,如圖:
那之因此稱爲 extend,是由於框架會直接把接口注入(mixin)到 controller 或者 think 對象中。這樣的好處是使用起來更方便,缺點是不一樣 extend 須要約定好不能重名。
最後
本文介紹的三個架構模式,你會發現幾乎在全部的Web框架實現都大同小異,這就是模式的好處。模式的意義相似於 IoC,我關注抽象和接口,抹平了具體語言特性下的細節問題,幫助咱們更好的學習,溝通和思考。
做者:蔡斯傑
https://75team.com/post/web-architecture-patterns-javascript