眾多的設計書籍都推薦用分層結構,這也幾乎是框架設計者的共識了。然而層是分
了,具體分幾層,還是一個值得決策的問題。我最熟悉的《責任驅動設計》和《領域驅
動設計》都推薦用四層,而且兩者的吻合度相當高,我受這兩本書的影響也最大,所以
很自然我的框架也分四層。
哪四層?表現(xiàn)層、應用層、領域層和技術層。
瀏覽器、PHP和數(shù)據(jù)庫是比較容易區(qū)分出來的物理上的三個層次。然而這只是物理
分層,跟邏輯分層還是有區(qū)別的。目前Ajax非常流行,而且很多應用也確實需要Ajax來
提供富交互功能,所以支持Ajax是相當自然的一件事。在物理層面上,Ajax屬于瀏覽器
層。實際上我對在瀏覽器端維護一個領域模型并不感興趣,原因是難以在兩者間保持同
步,也無謂地增加了復雜性。我完全可以直接在Action里處理,然后直接用PHP內(nèi)置的
JSON系列函數(shù)直接輸出數(shù)據(jù)供客戶端JavaScript進行eval調用。用JavaScript Helper
來生成Ajax,目前我還沒試過,具體是否支持,還要到時看情況而定。另外在資源定位
方面,我覺得REST的思想不錯,不過目前還不是很熟悉REST,所以這一塊打算暫時先放
一放,到時再重構以適應REST。
回到PHP這一物理層面上來。上面所說的四層架構,就是在PHP這一物理層次。
先說表現(xiàn)層。PHP對表現(xiàn)層的支持非常靈活非常方便,因為PHP最初就是用來做表現(xiàn)
層的。關于頁面的組織,我的方法是把一個HTML頁面分成兩類元素,一類是布局,另一
類是模板。跟現(xiàn)有的一些框架不一樣的是,我的模板沒有再細分為其它元素,如slot之
類。因為沒必要。一個布局對應一個有HTML輸出的Action,同時一個布局還對應多個模
板。注意,布局跟Action是一一對應的,但模板不同,模板是完全靈活的,在某個時候
它可以嵌入某個布局,在另一個時候可以嵌入另一個布局。這樣一來,模板就成了完全
可以復用的單元,而且經(jīng)過我的處理,每個模板可以在樹結構上從樹葉一直往上移到樹
根,以在多個Action、多個Module乃至多個App間的布局里共享。如果你熟悉JavaScript
的對象綁定,你就很容易理解一個對象做為另一個對象的某個屬性這一過程跟我的模板
對應布局這一過程非常相似,都是動態(tài)綁定的。
表現(xiàn)層由Action調用,而Action則處于應用層,多個Action組成一個Module,多個
Module組成一個Application(這一點參考自symfony,實際上在沒有接觸過symfony前
我就想到過這種組織方式)。這里有一個取舍問題,就是是把一個Action當成一個文件
處理,還是多個Action集中到一個文件來處理。symfony,Zend Framework采取的是一個
文件多個Action的方式。我之前采用的是一個文件一個Action的方式,后來想想這種方
式有一個缺點:難以在多個Action間共享某些計算,如Admin Module下的多個Action,
它們都需要檢查客戶端是否有有效的會話信息以確保用戶已經(jīng)登錄。在一個文件一個
Action的情況下,這些重復代碼必然會在多個Action間復制,除非你把這部分代碼抽取
出來放到某個地方,但問題是應該放在哪個地方?我想不到好的地方(你不可能把它們
放到抽象的Action父類,因為這些邏輯只是屬于某個模塊的,不是所有的Action子類都
有的),所以我打算采用一個文件多個Action的方法,并把這些重復代碼抽象成一個預
處理方法和后處理方法,當調用某個模塊的Action時,預先執(zhí)行這些代碼。在這些代碼
里,自然是校驗和計算,如果通不過,直接給Response對象發(fā)redirect消息,讓客戶端
重定向到某個地方。
除了調用表現(xiàn)層,Action還有哪些職責?實際上,Action的職責并不多,但它仍是
系統(tǒng)的核心所在,因為你的Action必須捕獲所有的用例。可能一個用例有多個Action參
與,也可能一個用例只有一個Action,這取決于你的用例分析。這是OOA階段的任務,
這里不多講,一個優(yōu)秀的分析人員必須能編寫高質量的用例(可以參考W.C的《編寫有
效用例》一書)。捕獲了用例后,Action有兩個任務:一,選取某個領域服務,讓該
領域服務處理某個邏輯事務;二,選取某個應用服務,讓該服務完成應用相關的任務,
如發(fā)送Email,把領域對象緩存進Cache源,把領域對象保存到Session數(shù)據(jù)源,等等。
這里的關鍵,是區(qū)分領域服務和應用服務,否則很難做出正確的設計。關于服務層,我
推薦閱讀三本書籍:一是《領域驅動設計》,這本書有一小節(jié)詳細地論述了服務層的設
計以及領域服務和應用服務的區(qū)分;二是《企業(yè)應用架構模式》,這本書有兩個地方講
述了服務層,講得比較細致;三是《J2EE核心模式》,里頭的“業(yè)務服務”模式就是領
域服務的精確描述,簡潔易懂。
應用服務操作技術層對象,那么領域服務操作什么對象?領域服務操作領域模型。
關于領域模型,有非常多的書有詳細的講述,前面提到的三本書都有詳細的講述,除此
外還有《UML與模式應用》,《UML面向對象建模與設計》等書,都有對領域模型的細致
講述。理解領域模型并運用領域模型是一種非常重要的技能,因為只有分析并提煉出合
適的領域模型,設計才不會偏離需求。需要注意的是,我個人對領域模型復雜化并沒有
好感,因為這樣一來映射到數(shù)據(jù)源將非常麻煩。我崇尚讓領域模型跟數(shù)據(jù)庫的E-R模型
一一對應,這樣一來無論理解還是修改,都非常簡單。另外一個可能影響設計的東西是
值對象。目前對這些比較細節(jié)方面的東西的處理,我還暫時沒有深入考慮,等設計到那
時再說。
需要注意的是,領域服務不能剝奪本應屬于領域模型的一部分業(yè)務邏輯,否則就混
淆了領域服務與領域模型的區(qū)別。比如一個領域模型:用戶,它有一個規(guī)格檢查:年齡
是否達到要求,這個規(guī)格檢查就不應該放到領域服務里,而應該直接加在領域模型上,
這也是非常自然的處理方法。此外,類似查詢用戶是否存在這樣的任務,就應該組織到
一個領域服務里,由Action調用該服務。領域服務并不知道是誰調用它(這符合分層的
基本原則,即上層對象可以調用它下層的對象,以及更下層的對象,但下層對象不能調
用上層對象,如果確實需要,至少也要用觀察者模式來解開耦合,以讓領域服務能更好
地在多個Action間重用。
領域層是由開發(fā)人員組織的,框架對領域層基本沒有做限制,開發(fā)者可以進行任意
的發(fā)揮,前面我只是提出一種我認為比較合適的組織方法。下一篇將講述框架內(nèi)最重要
的組成部分——技術層,以及技術層里的重中之重——持久層。