雖然從軟件架構(gòu)的角度,我們需要權(quán)衡取舍,需要克制在系統(tǒng)中過分追求技術(shù)。但是對于每一個有理想的軟件工程師,造輪子的情結(jié)是永恒的。各種各樣的輪子,始終是軟件工程師挑戰(zhàn)自我的必經(jīng)之路;web框架無疑應(yīng)該是一個比較好的開始,我自己也曾經(jīng)試圖造一個精美的輪子,但是始終實在業(yè)務(wù)發(fā)展壓倒一切的創(chuàng)業(yè)中,只能是點到為止。
所以,在有機會再次完整的去造輪子之前,通過自然語言,來整理一些思路;如果用畫畫來比喻,就是試著在畫竹子之前,先整理一下自己胸中竹子的形象。Web框架是一個好的選擇。
首先是Web框架的定義。Web框架應(yīng)該是使用某種特定的語言開發(fā),運行在服務(wù)器端,利用特定語言已經(jīng)提供的http庫或者http服務(wù)器完成自己的工作,處理用戶請求,返回主要是Web頁面在內(nèi)的資源。Web框架應(yīng)該是以編程語言API的形式出現(xiàn);提供一套契約設(shè)計好的腳手架,一些實用工具。
理論上說,支持Http協(xié)議的Web服務(wù)器應(yīng)該Web框架要討論的范疇,但是根據(jù)具體的架構(gòu)和技術(shù)選型,可以是跟web框架的定義或近或遠的。下面是我自己使用過或者有印象的一些主流Web框架:
- PHP語言:CodeIgniter、ThinkPHP、Laraval;php的框架,http服務(wù)器,基本上是不包含在Web框架核心中的,Apache httpd或者php-fpm+nginx處理請求響應(yīng)兩端,web框架不需要考慮端口監(jiān)聽,這個相當(dāng)于是一種解耦和。
- Node.js:Express,沒有接觸過太多其他的框架,在node.js的架構(gòu)下,node.js本身是有完整的http客戶端和服務(wù)器端能力的,我們完全可以用原生的node.js服務(wù)器端API,處理request,然后完成自己的業(yè)務(wù)邏輯,之后返回用戶要請求的數(shù)據(jù)。
- Java:Spring MVC、Spring Boot;實際上Spring Boot框架下,嚴格的Web框架還是Spring MVC;Spring Boot通過內(nèi)置的Jetty或者Tomcat來提供Http服務(wù)器的工作。在Java領(lǐng)域,應(yīng)該可以有兩種不同的思路,一種是基于Tomcat,Jetty作為Servlet容器,Web框架對Servlet進行封裝,并提供自己的API,開發(fā)人員在此API之上開發(fā);或者還可以使用Netty自己來編寫全功能框架。
- Python:Django、Tornado;Django提供了腳手架支持,集成了一套功能強大的ORM,可以通過代碼生成器,從代碼直接創(chuàng)建數(shù)據(jù)表。
- Ruby:rails;做過簡單的嘗試,沒有機會深入使用過,聽說rails的模式是敏捷開發(fā)最初的利器。
Web框架的組成
一般說道Web框架,我覺得應(yīng)該包含以下一些組件或者模塊:URL路由、MVC模式支持、特定的html模版語言、ORM、腳手架或者代碼生成器、配置管理。有些組件是必須,有些只是可選的,并不一定需要在框架中默認提供,開發(fā)者可以根據(jù)自己的需要自由集成需要的組件。
URL路由和控制器
url路由,是web框架的一個核心組件,資源的綁定,通過路由來配置。mvc的控制器層,在運行時視角,基本是從url路由來調(diào)度的。
說到路由,首先是對http服務(wù)器的理解,web mvc框架層,代碼層面上,有各種不同的資源文件,各種不同的鏈接,函數(shù)。通過邏輯上的分解,降低了系統(tǒng)復(fù)雜性,將系統(tǒng)各層面的代碼解耦到了不同的邏輯單元中。但是回到HTTP服務(wù)器的機制,其實物理上,http服務(wù)器都是單入口點,監(jiān)聽在特定端口的http服務(wù)器進程,所有的http請求都是從入口點程序開始執(zhí)行的,入口點首先是對tcp套接字的封裝,有的web框架會直接內(nèi)嵌完整的http服務(wù)器,全功能的可以作為http服務(wù)器來運行,有的需要借助容器或者是純web服務(wù)器來配合完成工作。
Tornado的原理跟nodejs差不多,入口程序調(diào)用http api,開啟監(jiān)聽在綁定端口的套接字,框架底層嵌入的http服務(wù)器,會處理網(wǎng)絡(luò)報文,將http協(xié)議的header解析出來,并且以api形式可以提供給基于框架的應(yīng)用調(diào)用(這個地方語言和框架的邊界不是十分清晰,語言底層一般會提供,框架可以自己在對api進行一層包裝,提供自己的api)。
在web框架中,url路由,基本上是和MVC的controller層緊密協(xié)作的,路由負責(zé)控制對象的初始化和調(diào)用。一般控制器的原理都是通過框架提供的抽象類來封裝一些公共的方法,然后子類實現(xiàn)具體的接口的方式工作的,將子類定義的公共方法,作為特定url指向的資源。
在tornado、express、laraval提供的url路由類型機制里,都是通過開發(fā)者自己配置維護一個路由表的方式,將url鏈接,跟靜態(tài)資源,或者MVC的控制器綁定起來。在請求到來的時候,單入口執(zhí)行過程中會有一個路由解析的過程,提取出url字符串,對字符串進行解析,然后跟路由表進行比較,使用字典是比較好的選擇,然后根據(jù)不同語言的特性,可以有不同的方式,實現(xiàn)對路由表指向的特定資源的代碼調(diào)用。
底層運行時,存在一個路由到控制器的運行時路由表,對于web框架來說是必須的。除了開發(fā)者可以直接配置的統(tǒng)一路由表的實現(xiàn),還可以有其他不同的機制。
幾種不同的URL路由機制
- 完全基于契約的CodeIgnitor模式,約定路由加載控制器的特定目錄,然后根據(jù)控制器的命名和共有方法的名字,來跟請求的url進行匹配。這個是基于php的語言特性支持,能提供的一種優(yōu)雅解決方案。
- 通過注解實現(xiàn)的,預(yù)加載路由表方式;Tomcat容器的在加載應(yīng)用的時候會在加載階段就加載所有的類,不同于PHP的運行時動態(tài)加載,所以需要占用更多的內(nèi)存。Spring MVC是基于Java的Servlet,Tomcat是運行Servlet的容器;個人的觀察Tomcat容器默認會提供Servlet、jsp等Jar包形式提供的類庫。不過在注冊特定uri資源的入口點,資源請求到來的時候,動態(tài)執(zhí)行路由指定的控制器上,是一樣的。
- 上面主要介紹的,顯示的用專門的路由表,由開發(fā)人員自己配置的模式;這種方式的話,個人感覺比較容易維護路由表。
HTML模版語言
理論上說,對于控制器層而言,其實html代碼,就是最終將字符串拼接起來,在對應(yīng)需要服務(wù)端數(shù)據(jù)的地方,拼接上對應(yīng)的存儲數(shù)據(jù)的變量。這種輸出的過程當(dāng)然是比較原始的,早期的web或者JSP可能存在著大量這種混合式的調(diào)用,在逐步分解出jsp的過程中,會在jsp文件中,以html格式做主框架,然后任何需要數(shù)據(jù)的地方,直接調(diào)用底層的數(shù)據(jù)查詢和處理代碼。這種方式,邏輯是很混亂的。
有了MVC,我們會在控制器層,完成所有數(shù)據(jù)查詢和組織,還有計算過程,以model的方式,將簡單的Map、List、還有直接對象的方式,將數(shù)據(jù)變量綁定到Model中。這樣View層的html模版中,只需要考慮怎么展示Model綁定的數(shù)據(jù)。
一般框架都會自帶或者內(nèi)嵌一個模版引擎,在控制器層,提供某種機制,將變量綁定到模版中。而模版中會提供很多處理數(shù)據(jù)顯示,進行格式化輸出,還有數(shù)值計算的使用函數(shù),都是通過模版引擎支持的特定語法實現(xiàn)的。
腳手架和代碼生成器
這個層面并非是必須的,express、rails、django,都是通過各自的包管理工具,可以直接初始化創(chuàng)建該框架應(yīng)用的腳手架。在腳手架領(lǐng)域,基于npm有專門的Yeoman,可以自定義各種腳手架。
一般來說,像django和laraval,都提供了migrant工具,就是一種代碼生成器類似的概念,可以直接將配置文件或者定義的模型層代碼,生成數(shù)據(jù)庫的表和字段。另外他們還提供了方便的代碼生成器功能,直接通過命令行下的命令就能快速生成控制器的代碼。
另外還有與業(yè)務(wù)邏輯更緊密一些的ORM層代碼生成器,離web框架的核心有一定距離,例如針對MyBatis,我們可以使用官方的mybatis-generator,編寫配置文件,直接從數(shù)據(jù)庫結(jié)構(gòu)生成DAO層對應(yīng)的一些CRUD代碼。
結(jié)語
理論上說,提供數(shù)據(jù)訪問支持,也是一個全功能web框架通常會提供的組件,不過數(shù)據(jù)訪問,可以專門在另外一個主題討論。本文主要就討論一下我對MVC、URL路由、html模版、還有腳手架的一些理解和思考,試著理清自己的思路,后續(xù),肯定會最終作為自己的一種能力提升的挑戰(zhàn)去造輪子。